Y1rannn 发表于 2022-1-25 11:40

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

### BabyVM

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

```python
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, 按这种格式把数据转换出来方便分析

```cpp
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;
    }
}
```

```ams
0x12,0x0, 0x0,

0x19
```

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

反调太多, 没能拿动调找到, 是通过找交叉引用找到的,其实这些内存都在一起,看到一个就能看到后面的![屏幕截图 2022-01-25 111714](https://tianyu.xin/usr/uploads/2022/01/634141456.png)

sub_6E2CC0是vm函数

```c
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](https://tianyu.xin/usr/uploads/2022/01/2293258022.png)

Mem_table是传入参数,也是Code_Segment,

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

```c
-----------------------------------
    -------------------------------
^   ^
|   |
m1m2
```

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

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

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

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

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

```python
CS = [
    略
]
cnt = 0
for IP in range(0, len(CS), 3) :
    RS = CS
    RT = CS
    print(cnt, end = " : ")
    if CS == 1 :
      print("mem[", RS, "] = ", RT, "")
    elif CS == 5 :
      print("mem64[++cnt] = mem[", RS, "]")
    elif CS == 7 :
      print("mem[", RS, "] += ", RT, "")
    elif CS == 0x12 :
      print("mem[", RS, "] ^= mem[", RT, "]")
    elif CS == 0x17 :
      print("mem[", RS, "] = Input()")
    elif CS == 0x18 :
      print("Print(lo32(mem[", RS, "]))")
    elif CS == 0x19 :
      print("Exit\nRestart 0x0 :")
      cnt = 0
      continue
    elif CS == 0x1a :
      print("ZF = mem[", RS, "] ==", RT)
      print("ZF1 = mem[", RS, "] <", RT)
    elif CS == 0x1e :
      print("JZ1", RS)
    elif CS == 0 :
      print("mem2] = ", RT )
    elif CS == 3 :
      print("mem[", RS, "] = mem2]")
    elif CS == 9 :
      print("mem[", RS, "] -= ", RT)
    elif CS == 4 :
      print("mem2] = mem[", RT, "]")
    elif CS == 6 :
      print("mem[", RS, "] = mem64")
    elif CS == 0x1C :
      print("JZ", RS)
    elif CS == 0x1d :
      print("JMP", RS)
    elif CS == 0x1f :
      print("JNZ", RS)
    elif CS == 0x11 :
      print("mem[", RS, "] ^=", RT)
    elif CS == 0xD :
      print("mem[", RS, "] <<=", RT)
    elif CS == 0x1B :
      print("ZF = mem[", RS, "] == mem[", RT, "]")
      print("ZF1 = mem[", RS, "] < mem[", RT, "]")
    else :
      print(hex(CS))
      break
    cnt += 1
```

```c
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] =255
2 : mem[ 2 ] +=1
3 : mem2] =547
4 : mem[ 2 ] +=1
5 : mem2] =571
6 : mem[ 2 ] +=1
7 : mem2] =567
8 : mem[ 2 ] +=1
9 : mem2] =567
10 : mem[ 2 ] +=1
11 : mem2] =587
12 : mem[ 2 ] +=1
13 : mem2] =555
14 : mem[ 2 ] +=1
15 : mem2] =251
16 : mem[ 2 ] +=1
17 : mem2] =555
18 : mem[ 2 ] +=1
19 : mem2] =547
20 : mem[ 2 ] +=1
21 : mem2] =591
22 : mem[ 2 ] +=1
23 : mem2] =239
24 : mem[ 2 ] +=1
25 : mem2] =567
26 : mem[ 2 ] +=1
27 : mem2] =239
28 : mem[ 2 ] +=1
29 : mem2] =591
30 : mem[ 2 ] +=1
31 : mem2] =591
32 : mem[ 2 ] +=1
33 : mem2] =547
34 : mem[ 2 ] +=1
35 : mem2] =547
36 : mem[ 2 ] +=1
37 : mem2] =571
38 : mem[ 2 ] +=1
39 : mem2] =567
40 : mem[ 2 ] +=1
41 : mem2] =255
42 : mem[ 2 ] +=1
43 : mem2] =563
44 : mem[ 2 ] +=1
45 : mem2] =563
46 : mem[ 2 ] +=1
47 : mem2] =563
48 : mem[ 2 ] +=1
49 : mem2] =567
50 : mem[ 2 ] +=1
51 : mem2] =587
52 : mem[ 2 ] +=1
53 : mem2] =563
54 : mem[ 2 ] +=1
55 : mem2] =591
56 : mem[ 2 ] +=1
57 : mem2] =555
58 : mem[ 2 ] +=1
59 : mem2] =555
60 : mem[ 2 ] +=1
61 : mem2] =587
62 : mem[ 2 ] +=1
63 : mem2] =239
64 : mem[ 2 ] +=1
65 : Exit
Restart 0x0 :
0 : mem[ 2 ] ^= mem[ 2 ]
1 : mem[ 0 ] = mem2]
2 : mem[ 0 ] -=99
3 : mem2] = 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
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
4 : mem2] = mem[ 0 ]
5 : mem[ 8 ] -=1
6 : JMP 19
7 : mem[ 0 ] = mem64
8 : ZF = mem[ 0 ] == 123
ZF1 = mem[ 0 ] < 123
9 : JNZ 3
10 : mem[ 0 ] = mem64
11 : ZF = mem[ 0 ] == 103
ZF1 = mem[ 0 ] < 103
12 : JNZ 3
13 : mem[ 0 ] = mem64
14 : ZF = mem[ 0 ] == 97
ZF1 = mem[ 0 ] < 97
15 : JNZ 3
16 : mem[ 0 ] = mem64
17 : ZF = mem[ 0 ] == 108
ZF1 = mem[ 0 ] < 108
18 : JNZ 3
19 : mem[ 0 ] = mem64
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]
25 : mem[ 6 ] = mem2]
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 之后和预置对比

```python
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 -= 99;
    Mem >>= 2;
    Mem ^= 66
    print(chr(Mem), end = "")
```

### EasyVM

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

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

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

输入函数一会再看

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

其中this~是AX-CX寄存器

```c
int __thiscall distributer(_DWORD *this, char *src, char *dst, int a4, int a5)
{
this = src;
this = dst;
this = a4;
this = a5;
while ( 2 )
{
    switch ( *(_BYTE *)this )
    {
      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((uint32), AX), IP += 4;
      case 0xCA:
      LOAD((uint32), BX), IP += 4;
      case 0xCB:
      LOAD((uint32), CX), IP += 4;
      case 0xCC:
      LOAD(Pre(uint8), AX)
      case 0xCD:
      LOAD(Pre(uint8), BX)
      case 0xCE:
      AX ^= BX
      case 0xCF:
      BX ^= AX
      case 0xD0:
      if AX == DST :
      DX = 1;
      else :
      AX >= DST ? DX = 2 : DX = 0;
      case 0xD1:
      if BX == DST :
      DX = 1;
      else :
      BX >= DST ? DX = 2 : DX = 0;
      case 0xD2:
      if CX == :
      DX = 1;
      else :
      CX >= ? DX = 2 : DX = 0;
      IP += 4
      case 0xD3:
      if DX == 1 :
          IP += (uint8);
      IP ++;
      case 0xD4:
      if DX != 1 :
          IP += (uint8);
      IP ++;
      case 0xFE:
      return 0;
      case 0xFF:
      return 1;
      default:
      print((int)aCmdError);
      return 0;
      IP ++;
    }
}
}
```

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

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

```c
0x0 : LD32 BX, 0
IP += 5
0x5 : LD32 CX, 0
IP += 5
0xa : LD8 AX, Inp      
IP += 1
0xb : XOR BX AX
IP += 1
0xc : LD32 AX, 0xEE
IP += 5
0x11 : XOR BX AX
IP += 1
0x12 : CMP BX, TAB      
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要等于明文

脚本

```python
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 =
for i in range(1, len(Target), 1) :
    print(chr(Target ^ 0xEE ^ Target ^ salt), end = "")
print()
#ZmxhZ3syNTg2ZGM3Ni05OGQ1LTQ0ZTItYWQ1OC1kMDZlNjU1OWQ4MmF9
```

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

Elaineliu 发表于 2022-1-26 10:19

不明觉厉,好高的水平

深蓝星空 发表于 2022-1-28 00:50

多谢楼主分享!!!

Tokameine 发表于 2022-1-28 09:54

现在再看wp觉得好像不是很难,但当时做的时候倒是做得死去活来的......再反过来想想,果然自己还是太菜了(瘫

elevo 发表于 2022-2-1 21:41

迷迷糊糊的呢

无言Y 发表于 2022-2-21 11:40

学习一下

xunxunmimi0936 发表于 2022-2-22 16:07

感谢分享。
页: [1]
查看完整版本: [HWS2022硬件安全 x DAS Jan] EasyVM + BabyVM