[CTF/Reverse] [HWS2022硬件安全 x DAS Jan] EasyVM + BabyVM
侧边栏壁纸
  • 累计撰写 65 篇文章
  • 累计收到 3 条评论

[CTF/Reverse] [HWS2022硬件安全 x DAS Jan] EasyVM + BabyVM

x1n
x1n
2022-01-25 / 0 评论 / 125 阅读 / 正在检测是否收录...

BabyVM

进来跟着main一顿跳, 跳到412CC0, 先把花指令去掉

from ida_bytes import *
base = 0x412DBC
to = 0x413937

for i in range(base, to) :
    if get_bytes(i, 5) == b'\x74\x03\x75\x01\xe8' :
        patch_bytes(i, b'\x90\x90\x90\x90\x90')

每条指令用了三个64bit, 按这种格式把数据转换出来方便分析

int main() {
    unsigned long long * p = (unsigned long long *) ida_chars;
    for(int i = 0; i < 888; i += 24) {
        printf("0x%llx 0x%llx, 0x%llx\n", *p, *(p+1), *(p+2) != -1ll ? *(p+2) : 0);
        p += 3;
    }
}
0x12,0x0, 0x0,
略
0x19

写了部分脚本之后没找到对比, 那就是还有OPCode, 我太天真了, 以为只用这些, 实际应该是我去花指令之后声明函数没太声明好.下面是提出来的内存, 太长了没必要都挂上, 0x19是每段的结束.

反调太多, 没能拿动调找到, 是通过找交叉引用找到的,其实这些内存都在一起,看到一个就能看到后面的屏幕截图 2022-01-25 111714

sub_6E2CC0是vm函数

0x12,0x2, 0x2,
略
0x19, 0, 0,
0x12,0x2, 0x2,
略
0x19, 0, 0,
0x6,0x0, 0x0,
略
0x19,0xffffffffffffffff, 0x0,
略
0x19,0xffffffffffffffff, 0x0

三个64bit我分别定义成了opcode, rs, rt, 差不多就是mips那种

这个分析的时候没把寄存器看成寄存器, 实际的寄存器就是这个mem1和mem2, 下面会解释 ,核心vm的操作都写在下面的脚本里了,我主要解释一下命名,可以结合你的IDA对照着看, 因为这边IDA数据库改掉之后没有原始哑名了

屏幕截图 2022-01-25 112135

Mem_table是传入参数,也是Code_Segment,

mem1和mem2共同构成了脚本中的mem,你可能看到mem1和mem2使用的偏移都在基础寻址上乘了2, 这是一种类似储存器位扩展的形式,它是这样的

-----------------------------------
    -------------------------------
^   ^
|   |
m1  m2

m1永远奇数, m2永远偶数, 由此两个int就可以交叉成一个64位, 我觉得它更像一个寄存器的结构, 我们可以分别读取高低32位

这里的mem3是一个类似栈的结构,在下面的脚本中被我称为了mem64, 原因是原vm代码中它都是以64位而非上面的交叉结构存在的

脚本中还有一个mem2, 它不同于IDA里被我改名的mem2, 是case 0x5, 0x6中 出现的另一段内存, 实际上,它的性质才更像内存. 但是改命名太麻烦了,能看懂就好

另外还有两个寄存器, 分别被我命名成了 cnt 和 ZF/ZF1, 前者是栈寄存器,后者是判断寄存器.

非常传统的vm题, 我没有其他可以说的了

CS = [
    略
]
cnt = 0
for IP in range(0, len(CS), 3) :
    RS = CS[IP+1]
    RT = CS[IP+2]
    print(cnt, end = " : ")
    if CS[IP] == 1 :
        print("mem[", RS, "] = ", RT, "")
    elif CS[IP] == 5 :
        print("mem64[++cnt] = mem[", RS, "]")
    elif CS[IP] == 7 :
        print("mem[", RS, "] += ", RT, "")
    elif CS[IP] == 0x12 :
        print("mem[", RS, "] ^= mem[", RT, "]")
    elif CS[IP] == 0x17 :
        print("mem[", RS, "] = Input()")
    elif CS[IP] == 0x18 :
        print("Print(lo32(mem[", RS, "]))")
    elif CS[IP] == 0x19 :
        print("Exit\nRestart 0x0 :")
        cnt = 0
        continue
    elif CS[IP] == 0x1a :
        print("ZF = mem[", RS, "] ==", RT)
        print("ZF1 = mem[", RS, "] <", RT)
    elif CS[IP] == 0x1e :
        print("JZ1", RS)
    elif CS[IP] == 0 :
        print("mem2[mem[", RS, "]] = ", RT )
    elif CS[IP] == 3 :
        print("mem[", RS, "] = mem2[mem1[", RT, "]]")
    elif CS[IP] == 9 :
        print("mem[", RS, "] -= ", RT)
    elif CS[IP] == 4 :
        print("mem2[mem[", RS, "]] = mem[", RT, "]")
    elif CS[IP] == 6 :
        print("mem[", RS, "] = mem64[cnt--]")
    elif CS[IP] == 0x1C :
        print("JZ", RS)
    elif CS[IP] == 0x1d :
        print("JMP", RS)
    elif CS[IP] == 0x1f :
        print("JNZ", RS)
    elif CS[IP] == 0x11 :
        print("mem[", RS, "] ^=", RT)
    elif CS[IP] == 0xD :
        print("mem[", RS, "] <<=", RT)
    elif CS[IP] == 0x1B :
        print("ZF = mem[", RS, "] == mem[", RT, "]")
        print("ZF1 = mem[", RS, "] < mem[", RT, "]")
    else :
        print(hex(CS[IP]))
        break
    cnt += 1
0 : mem[ 0 ] ^= mem[ 0 ]
1 : mem[ 1 ] ^= mem[ 1 ]
2 : mem[ 2 ] ^= mem[ 2 ]
3 : mem[ 3 ] ^= mem[ 3 ]
4 : mem[ 6 ] ^= mem[ 6 ]
5 : mem[ 7 ] ^= mem[ 7 ]
6 : mem[ 0 ] =  105
7 : mem[ 1 ] =  110
8 : mem[ 2 ] =  112
9 : mem[ 3 ] =  117
10 : mem[ 6 ] =  116
11 : mem[ 7 ] =  32
12 : Print(lo32(mem[ 0 ]))
13 : Print(lo32(mem[ 1 ]))
14 : Print(lo32(mem[ 2 ]))
15 : Print(lo32(mem[ 3 ]))
16 : Print(lo32(mem[ 6 ]))
17 : Print(lo32(mem[ 7 ]))
18 : mem[ 0 ] =  102
19 : mem[ 1 ] =  108
20 : mem[ 2 ] =  97
21 : mem[ 3 ] =  103 
22 : mem[ 6 ] =  58
23 : mem[ 7 ] =  32
24 : Print(lo32(mem[ 0 ]))
25 : Print(lo32(mem[ 1 ]))
26 : Print(lo32(mem[ 2 ]))
27 : Print(lo32(mem[ 3 ]))
28 : Print(lo32(mem[ 6 ]))
29 : Print(lo32(mem[ 7 ]))
30 : mem[ 1 ] ^= mem[ 1 ]
31 : mem[ 0 ] = Input()
32 : mem64[++cnt] = mem[ 0 ]
33 : mem[ 1 ] +=  1
34 : ZF = mem[ 1 ] == 38
ZF1 = mem[ 1 ] < 38
35 : JZ1 31
36 : Exit
Restart 0x0 :
0 : mem[ 2 ] ^= mem[ 2 ]
1 : mem2[mem[ 2 ]] =  255
2 : mem[ 2 ] +=  1
3 : mem2[mem[ 2 ]] =  547
4 : mem[ 2 ] +=  1
5 : mem2[mem[ 2 ]] =  571
6 : mem[ 2 ] +=  1
7 : mem2[mem[ 2 ]] =  567
8 : mem[ 2 ] +=  1
9 : mem2[mem[ 2 ]] =  567
10 : mem[ 2 ] +=  1
11 : mem2[mem[ 2 ]] =  587
12 : mem[ 2 ] +=  1
13 : mem2[mem[ 2 ]] =  555
14 : mem[ 2 ] +=  1
15 : mem2[mem[ 2 ]] =  251
16 : mem[ 2 ] +=  1
17 : mem2[mem[ 2 ]] =  555
18 : mem[ 2 ] +=  1
19 : mem2[mem[ 2 ]] =  547
20 : mem[ 2 ] +=  1
21 : mem2[mem[ 2 ]] =  591
22 : mem[ 2 ] +=  1
23 : mem2[mem[ 2 ]] =  239
24 : mem[ 2 ] +=  1
25 : mem2[mem[ 2 ]] =  567
26 : mem[ 2 ] +=  1
27 : mem2[mem[ 2 ]] =  239
28 : mem[ 2 ] +=  1
29 : mem2[mem[ 2 ]] =  591
30 : mem[ 2 ] +=  1
31 : mem2[mem[ 2 ]] =  591
32 : mem[ 2 ] +=  1
33 : mem2[mem[ 2 ]] =  547
34 : mem[ 2 ] +=  1
35 : mem2[mem[ 2 ]] =  547
36 : mem[ 2 ] +=  1 
37 : mem2[mem[ 2 ]] =  571
38 : mem[ 2 ] +=  1
39 : mem2[mem[ 2 ]] =  567
40 : mem[ 2 ] +=  1
41 : mem2[mem[ 2 ]] =  255
42 : mem[ 2 ] +=  1
43 : mem2[mem[ 2 ]] =  563
44 : mem[ 2 ] +=  1
45 : mem2[mem[ 2 ]] =  563
46 : mem[ 2 ] +=  1
47 : mem2[mem[ 2 ]] =  563
48 : mem[ 2 ] +=  1
49 : mem2[mem[ 2 ]] =  567
50 : mem[ 2 ] +=  1
51 : mem2[mem[ 2 ]] =  587
52 : mem[ 2 ] +=  1
53 : mem2[mem[ 2 ]] =  563
54 : mem[ 2 ] +=  1
55 : mem2[mem[ 2 ]] =  591
56 : mem[ 2 ] +=  1
57 : mem2[mem[ 2 ]] =  555
58 : mem[ 2 ] +=  1
59 : mem2[mem[ 2 ]] =  555
60 : mem[ 2 ] +=  1
61 : mem2[mem[ 2 ]] =  587
62 : mem[ 2 ] +=  1
63 : mem2[mem[ 2 ]] =  239
64 : mem[ 2 ] +=  1
65 : Exit
Restart 0x0 :
0 : mem[ 2 ] ^= mem[ 2 ]
1 : mem[ 0 ] = mem2[mem1[ 2 ]]
2 : mem[ 0 ] -=  99
3 : mem2[mem[ 2 ]] = mem[ 0 ]
4 : mem[ 2 ] +=  1
5 : ZF = mem[ 2 ] == 32
ZF1 = mem[ 2 ] < 32
6 : JZ1 1
7 : Exit
    
Restart 0x0 :
0 : mem[ 0 ] = mem64[cnt--]
1 : ZF = mem[ 0 ] == 125
ZF1 = mem[ 0 ] < 125
2 : JZ 18
3 : mem[ 0 ] =  119
4 : mem[ 1 ] =  114
5 : mem[ 2 ] =  111
6 : mem[ 3 ] =  110
7 : mem[ 6 ] =  103
8 : mem[ 7 ] =  33
9 : Print(lo32(mem[ 0 ]))
10 : Print(lo32(mem[ 1 ]))
11 : Print(lo32(mem[ 2 ]))
12 : Print(lo32(mem[ 3 ]))
13 : Print(lo32(mem[ 6 ]))
14 : Print(lo32(mem[ 7 ]))
15 : mem[ 0 ] =  10
16 : Print(lo32(mem[ 0 ]))
17 : Exit
    
Restart 0x0 :
0 : mem[ 8 ] =  256
1 : ZF = mem[ 8 ] == 225
ZF1 = mem[ 8 ] < 225
2 : JZ1 25
3 : mem[ 0 ] = mem64[cnt--]
4 : mem2[mem[ 8 ]] = mem[ 0 ]
5 : mem[ 8 ] -=  1
6 : JMP 19
7 : mem[ 0 ] = mem64[cnt--]
8 : ZF = mem[ 0 ] == 123
ZF1 = mem[ 0 ] < 123
9 : JNZ 3
10 : mem[ 0 ] = mem64[cnt--]
11 : ZF = mem[ 0 ] == 103
ZF1 = mem[ 0 ] < 103
12 : JNZ 3
13 : mem[ 0 ] = mem64[cnt--]
14 : ZF = mem[ 0 ] == 97
ZF1 = mem[ 0 ] < 97
15 : JNZ 3
16 : mem[ 0 ] = mem64[cnt--]
17 : ZF = mem[ 0 ] == 108
ZF1 = mem[ 0 ] < 108
18 : JNZ 3
19 : mem[ 0 ] = mem64[cnt--]
20 : ZF = mem[ 0 ] == 102
ZF1 = mem[ 0 ] < 102
21 : JNZ 3

22 : mem[ 9 ] ^= mem[ 9 ]
23 : mem[ 10 ] =  225
24 : mem[ 7 ] = mem2[mem1[ 9 ]]
25 : mem[ 6 ] = mem2[mem1[ 10 ]]
26 : mem[ 6 ] ^= 66
27 : mem[ 6 ] <<= 2
28 : ZF = mem[ 6 ] == mem[ 7 ]
ZF1 = mem[ 6 ] < mem[ 7 ]
29 : JNZ 3
30 : mem[ 9 ] +=  1
31 : mem[ 10 ] +=  1
32 : ZF = mem[ 9 ] == 32
ZF1 = mem[ 9 ] < 32
33 : JZ1 42

34 : mem[ 0 ] =  99
35 : mem[ 1 ] =  111
36 : mem[ 2 ] =  114
37 : mem[ 3 ] =  114
38 : mem[ 6 ] =  101
39 : mem[ 7 ] =  99
40 : Print(lo32(mem[ 0 ]))
41 : Print(lo32(mem[ 1 ]))
42 : Print(lo32(mem[ 2 ]))
43 : Print(lo32(mem[ 3 ]))
44 : Print(lo32(mem[ 6 ]))
45 : Print(lo32(mem[ 7 ]))
46 : mem[ 0 ] =  116 
47 : mem[ 1 ] =  108
48 : mem[ 2 ] =  121
49 : mem[ 3 ] =  33
50 : mem[ 6 ] =  10
51 : Print(lo32(mem[ 0 ]))
52 : Print(lo32(mem[ 1 ]))
53 : Print(lo32(mem[ 2 ]))
54 : Print(lo32(mem[ 3 ]))
55 : Print(lo32(mem[ 6 ]))
56 : Exit
Restart 0x0 :

先把输入读到mem64, 再在mem2预置一串数, 并全减掉99, flag ^ 66 再 左移2 之后和预置对比

Mem = [
    255, 547, 571, 567, 567,
    587, 555, 251, 555, 547,
    591, 239, 567, 239, 591,
    591, 547, 547, 571, 567,
    255, 563, 563, 563, 567,
    587, 563, 591, 555, 555,
    587, 239
]

for i in range(len(Mem)) :
    Mem[i] -= 99;
    Mem[i] >>= 2;
    Mem[i] ^= 66
    print(chr(Mem[i]), end = "")

EasyVM

进来没有main, 有两条花指令, nop掉

401610看上去像反调, 把jnz nop掉, 强行返回2.0

4012F0初始化, 把输入扔4011E0里, 返回是分发器的最后一个参数

输入函数一会再看

申请了一段地址, 作为函数表, 接下来一个一个函数分析, 函数分析结果写在下面的C脚本里了

其中this[2]~[4]是AX-CX寄存器

int __thiscall distributer(_DWORD *this, char *src, char *dst, int a4, int a5)
{
  this[1] = src;
  this[6] = dst;
  this[7] = a4;
  this[8] = a5;
  while ( 2 )
  {
    switch ( *(_BYTE *)this[1] )
    {
      case 0xC0:
        AX ++;
      case 0xC1:
        BX ++;
      case 0xC2:
        CX ++;
      case 0xC3:
        AX = BX;
      case 0xC4:
      AX = CX;
      case 0xC5:
      BX = AX;
      case 0xC6:
      BX = CX;
      case 0xC7:
      CX = AX;
      case 0xC8:
      CX = BX;
      case 0xC9:
      LOAD([IP+1](uint32), AX), IP += 4;
      case 0xCA:
      LOAD([IP+1](uint32), BX), IP += 4;
      case 0xCB:
      LOAD([IP+1](uint32), CX), IP += 4;
      case 0xCC:
      LOAD(Pre(uint8)[CX], AX)
      case 0xCD:
      LOAD(Pre(uint8)[CX], BX)
      case 0xCE:
        AX ^= BX  
      case 0xCF:
        BX ^= AX
      case 0xD0:
      if AX == DST[CX] :
        DX = 1;
      else :
        AX >= DST ? DX = 2 : DX = 0;
      case 0xD1:
      if BX == DST[CX] :
        DX = 1;
      else :
        BX >= DST[CX] ? DX = 2 : DX = 0;
      case 0xD2:
      if CX == [IP+1] :
        DX = 1;
      else :
        CX >= [IP+1]  ? DX = 2 : DX = 0;
      IP += 4
      case 0xD3:
        if DX == 1 :
          IP += [IP+1](uint8);
        IP ++;
      case 0xD4:
        if DX != 1 :
          IP += [IP+1](uint8);
        IP ++;
      case 0xFE:
        return 0;
      case 0xFF:
        return 1;
      default:
        print((int)aCmdError);
        return 0;
        IP ++;
    }
  }
}

输入大概就是B64解码, 返回一个首地址, 目的是通过JMP跳到 0xFF

内存拽出来分析成汇编, 汇编逻辑可以直接看下面的脚本

0x0 : LD32 BX, 0
IP += 5
0x5 : LD32 CX, 0
IP += 5
0xa : LD8 AX, Inp[CX]       
IP += 1
0xb : XOR BX AX
IP += 1
0xc : LD32 AX, 0xEE
IP += 5
0x11 : XOR BX AX
IP += 1
0x12 : CMP BX, TAB[CX]      
IP += 1
0x13 : JPD 0x1
IP += 2
0x15 : FAILED
IP += 1
0x16 : INC CX
IP += 1
0x17 : CMP CX, 0x39
IP += 5
0x1c : JND 0xEC // 0x1C + 0xEC + 2 & 0xFF = 0xA
IP += 2
0x1e : SUCCESS
IP += 1

逻辑就是0x38位每位异或前一位结果, 再异或0xEE要等于明文

脚本

Target = [
  0, 0xBE, 0x36, 0xAC, 0x27, 0x99, 0x4F, 0xDE, 0x44, 0xEE, 0x5F, 
  0xDA, 0x0B, 0xB5, 0x17, 0xB8, 0x68, 0xC2, 0x4E, 0x9C, 0x4A, 
  0xE1, 0x43, 0xF0, 0x22, 0x8A, 0x3B, 0x88, 0x5B, 0xE5, 0x54, 
  0xFF, 0x68, 0xD5, 0x67, 0xD4, 0x06, 0xAD, 0x0B, 0xD8, 0x50, 
  0xF9, 0x58, 0xE0, 0x6F, 0xC5, 0x4A, 0xFD, 0x2F, 0x84, 0x36, 
  0x85, 0x52, 0xFB, 0x73, 0xD7, 0x0D, 0xE3
]
salt = [0xD, 0xC, 0xB, 0xA]
for i in range(1, len(Target), 1) :
    print(chr(Target[i] ^ 0xEE ^ Target[i-1] ^ salt[3-(i-1)%4]), end = "")
print()
#ZmxhZ3syNTg2ZGM3Ni05OGQ1LTQ0ZTItYWQ1OC1kMDZlNjU1OWQ4MmF9

注意到input里给连续四位分别异或了ABCD, 解码要异或回来, 解base64即可

0

评论 (0)

取消