0%

WEIRD_VM

WEIRD_VM

看到空白出了一道vm题,就做了一下,感觉出的还是很好的,而且拿到了3血。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned __int16 *v3; // rax
__int64 v4; // rdx
unsigned __int16 *opcode; // rbx
int result; // eax
__int64 v7; // rdx
__int64 v8; // r8
__int64 v9; // rcx
__int64 v10; // rax
__int64 v11; // r9
unsigned __int16 v12; // dx

v3 = (unsigned __int16 *)VirtualAlloc(0i64, 0x20000ui64, 0x1000u, 4u);
qword_140029990 = (__int64)v3;
opcode = v3;
if ( v3 )
{
sub_140002830(v3 + 300, &unk_140024A40, 15790i64);
*opcode = 300;
do
{
if ( opcode[4] == 1 )
{
v7 = opcode[3];
opcode[4] = 0;
printf("%c", v7);
opcode = (unsigned __int16 *)qword_140029990;
}
if ( opcode[6] == 1 )
{
opcode[6] = 0;
scanf("%c", opcode + 5);
opcode = (unsigned __int16 *)qword_140029990;
}
v8 = *opcode;
v9 = opcode[v8 + 1];
v10 = opcode[v8];
v11 = opcode[v8 + 2];
*opcode = v8 + 3;
v12 = ~(opcode[v10] | opcode[v9]);
opcode[v11] = v12;
opcode[1] = __ROL2__(v12, 1);
}
while ( *opcode != 0xFFFF );
VirtualFree(opcode, 0i64, 0x8000u);
result = 0;
qword_140029990 = 0i64;
}
else
{
printf("Init vm failed\n", v4);
return 0;
}
return result;
}

这类的vm感觉设计非常巧妙,我们dump出来opcode,opcode很多,所以只挑一些重点在此分析:

先看明白一些重要的步骤:
如最后一位是7,下一行前两位是7,那么可以认为是传值,即0x1ff2的值传到opcode[3]中

1
2
0x1ff2 0x1ff2 0x7
0x7 0x7 0x3 //a[3] 赋值a[7]P

典型的跳转函数,最后一位是0,就代表要jmp了:

1
2
3
0x7 0x7 0x0 
0x773 0x0
0x0

接下来从头开始看:

这个板块就是printf输出,打印字符串,这个板块统一的模式就是下图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
0x1ff2 0x1ff2 0x7
0x7 0x7 0x3 //a[3] 赋值a[7]P
0x138 0x138 0x7
0x7 0x7 0x0 A[0] = 0X13A,jmp a[0]
0x13a 0x1 //跳过
0x139,0x139 0x7
0x7,0x7 0x4 //a[4] =1
0x1fef,0x1fef 0x7
0x7 0x7 0x3 // l
0x14c 0x14c 0x7
0x7 0x7 0x0//jmp
0x14e 0x1 //跳过
0x14d 0x14d 0x7
0x7 0x7 0x4
0x1ff0 0x1ff0 0x7
0x7 0x7 0x3
0x160 0x160 0x7
0x7 0x7 0x0
0x162 0x1
0x161 0x161 0x7
0x7 0x7 0x4

略过printf后,接下来是scanf环节,scanf拥有字符后会传进0x1fc7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//scanf
0x2fe 0x2fe 0x7
0x7 0x7 0x0//jmp
0x300 0x307 //跳过
0x2ff 0x2ff 0x7
0x7 0x7 0x0 //跳过
0x0
0x30d 0x30d 0x7
0x7 0x7 0x0//跳过
0x30f 0x1
0x30e 0x30e 0x7
0x7 0x7 0x6
0x5 0x5 0x7 //输入传到a[7]
0x7 0x7 0x1fc7 //写入flag

从这里面可以知道字符串长度,可是我们最关心的是字符串如何进行加密和对比,我们仔细观察后面对每一位的加密会发现总共有三种加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0x1f99 0x1f99 0x6c0 //flag运算后存入0x6c0
0x1faa 0x1faa 0x6c1 //这个值存入0x6c1
0x6ce 0x6ce 0x7 //跳转
0x7 0x7 0x0
0x6d1 0x0
0x0
0x1f99 0x1f99 0x6cf //flag运算后存入0x6cf
0x6c1 0x6c1 0x6d0 //固定的这个值要搞两下
0x6cf 0x6d0 0x7 /0x6
0x7 0x7 0x7 //0xfff9
0x7 0x7 0x6c1 //固定值被覆盖0x6
0x6e6 0x6e6 0x7
0x7 0x7 0x0
0x6e9 0x0
0x0
0x1faa 0x1faa 0x6e7 //固定值存在0x6e7
0x6c0 0x6c0 0x6e8 //flag运算后存入0x6c0的值再搞一下
0x6e7 0x6e8 0x7
0x7 0x7 0x7
0x7 0x7 0x6c0 //0x6c0存入0x6e7 0x6e8 0x7 值
0x6c0 0x6c1 0x7
0x7 0x7 0x1f99 //覆盖原flag加密后的值
0x1f99 0x306 0x7 //加密后的跟0搞
0x7 0x7 0x306 //存入306

上面是刚做题时候分析的,其实第一个加密还是很简单的,我们用简单表示一下,也就是经过15次ROL加密,然后对比:

1
2
3
4
5
6
7
8
9
10
for k in range(0,127):
a[5] = k
a[1] = ROL(k,1)
a[5] = a[1]
for i in range(14):
a[1] = ROL(a[5],1)
a[5] = a[1]
#print(hex(a[5]))
if(a[5] == 0x22):
print(chr(k))

第二个加密是很长也是最难分析的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
0x1fc9 0x1fc9 0x762 //0X53->0XFFAC
0x1fa2 0x1fa2 0x763 //0X12 ->0XFFED
0x770 0x770 0x7
0x7 0x7 0x0
0x773 0x0
0x0
0x1fc9 0x1fc9 0x771 //0X53 ->0XFFAC//////////////////////////X
0x763 0x763 0x772 //0xFFED->0X12 ///////////////////////0X12
0x771 0x772 0x7 //0X41
0x7 0x7 0x7
0x7 0x7 0x763
0x788 0x788
0x7 0x7 0x7
0x0 0x78b 0x0
0x0
0x1fa2 0x1fa2 0x789 //0X12->>0XFFED
0x762 0x762 0x78a //0XFFAC->0X53
0x789 0x78a 0x7 //0
0x7 0x7 0x7
0x7 0x7 0x762
0x762 0x763 0x7 //0
0x7 0x7 0x1f99
0x7a6 0x7a6 0x7
0x7 0x7
0x0 0x7a9 0x0
0x0
0x1f99 0x1f99
0x7a7 0x1fb8 0x1fb8 /////////////第一等是答案 0x41
0x7a8 0x7b5 0x7b5
0x7 0x7 0x7
0x0 0x7b8 0x0
0x0
0x1f99 0x1f99
0x7b6 0x7a8 0x7a8 //////////////////第二个等式答案0
0x7b7 0x7b6 0x7b7
0x7 0x7 0x7
0x7 0x7 0x7
0x7a8 0x7cd 0x7cd
0x7 0x7 0x7
0x0 0x7d0 0x0
0x0
0x1fb8 0x1fb8 /////////////第一等是答案 0x41
0x7ce 0x7a7 0x7a7
0x7cf 0x7ce 0x7cf
0x7 0x7 0x7ROL(a[5],1)
0x7 0x7 0x7
0x7a7 0x7a7 0x7a8
0x7 0x7 0x7
0x1f99 0x1f99 0x306
0x7 0x7 0x7
0x306

看出来其实也比较简单,就是两组数分别做运算:

1
2
3
4
5
6
7
8
for i in range(27,127):
x = ~(i|i) & 0XFFFF
xx = ~(x|0X33) & 0XFFFF
y = ~(0X33|0X33) & 0XFFFF
yy = ~(y|i) & 0XFFFF
p = ~(xx|yy)&0xffff
if((~(p|p)&0xffff == 0x41)):
print(chr(i))

第三种是最简单的加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
0x1fec 0x1fec
0x7 0x7 0x7
0x1fec 0x1 0x1
0x7 0x7 0x7
0x1f99 0x1d03 0x1d03
0x7 0x7 0x7
0x0 0x1d06 0x0
0x0 0x1f99 0x1f99
0x1d04 0x1fc4 0x1fc4
0x1d05 0x1d12 0x1d12
0x7 0x7 0x7
0x0 0x1d15 0x0
0x0 0x1f99 0x1f99
0x1d13 0x1d05 0x1d05
0x1d14 0x1d13 0x1d14
0x7 0x7 0x7
0x7 0x7 0x7
0x1d05 0x1d2a 0x1d2a
0x7 0x7 0x7
0x0 0x1d2d 0x0
0x0 0x1fc4 0x1fc4
0x1d2b 0x1d04 0x1d04
0x1d2c 0x1d2b 0x1d2c
0x7 0x7 0x7
0x7 0x7 0x7
0x1d04 0x1d04 0x1d05
0x7 0x7 0x7
0x1f99 0x1f99 0x306
0x7 0x7 0x7
0x306
0x1d4e 0x1d4e 0x7
0x7 0x7 0x0
0x1d50 0x0

会发现加密比较明显,就是做了一次ROL:

1
2
3
for i in range(27,127):
if(0X60 == ROL(i,1)):
print(chr(i))

只要把这三种加密搞懂,就可以进行爆破来得出每一位的flag。

最后的地方存了变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//存在这个地方
0x0 0x1 0x86
0x6 0x7 0x88
0x8 0x8c 0xc
0x12 0x1b 0x1c
0xa0 0x8022 0x23
0x22 0xa6 0x27
0x29 0x2a 0x8029
0xac 0x802f 0x32
0x33 0x37 0x39
0x3c 0x803d 0x3e
0xbe 0x41 0x43
0xca 0x4f 0x53
0x5e 0x5f 0x60
0x62 0xe4 0x6e
0x6f 0xfa 0x7b
0x7e

//写flag进去
0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x0 0x0 0x0
0x72 0x6f 0x6c
0x65 0x61 0x50
0x73 0x57 0x64
0x74 0x67 0x3a
0x20 0x43 0x66
0x6e 0x52 0x69
0x70 0x75 0x79

他这个rol加密跟平常见的好像还不太一样:

1
2
3
4
5
def ROL(i,index):
tmp = bin(i)[2:].rjust(16, "0")
for _ in range(index):
tmp = tmp[1:] + tmp[0]
return int(tmp, 2) &0xffff

得到flag:NSSCTF{Pr0_ReVEr3Er_F1n1SHeD_We1RD_Vm}