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; __int64 v4; unsigned __int16 *opcode; int result; __int64 v7; __int64 v8; __int64 v9; __int64 v10; __int64 v11; unsigned __int16 v12; v3 = (unsigned __int16 *)VirtualAlloc(0 i64, 0x20000 ui64, 0x1000 u, 4u ); qword_140029990 = (__int64)v3; opcode = v3; if ( v3 ) { sub_140002830(v3 + 300 , &unk_140024A40, 15790 i64); *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, 0 i64, 0x8000 u); result = 0 ; qword_140029990 = 0 i64; } 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
典型的跳转函数,最后一位是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 0x138 0x138 0x7 0x7 0x7 0x0 A[0 ] = 0X13A ,jmp a[0 ]0x13a 0x1 0x139 ,0x139 0x7 0x7 ,0x7 0x4 0x1fef ,0x1fef 0x7 0x7 0x7 0x3 0x14c 0x14c 0x7 0x7 0x7 0x0 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 0x2fe 0x2fe 0x7 0x7 0x7 0x0 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 0x7 0x7 0x1fc7
从这里面可以知道字符串长度,可是我们最关心的是字符串如何进行加密和对比,我们仔细观察后面对每一位的加密会发现总共有三种加密:
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 0x1faa 0x1faa 0x6c1 0x6ce 0x6ce 0x7 0x7 0x7 0x0 0x6d1 0x0 0x0 0x1f99 0x1f99 0x6cf 0x6c1 0x6c1 0x6d0 0x6cf 0x6d0 0x7 /0x6 0x7 0x7 0x7 0x7 0x7 0x6c1 0x6e6 0x6e6 0x7 0x7 0x7 0x0 0x6e9 0x0 0x0 0x1faa 0x1faa 0x6e7 0x6c0 0x6c0 0x6e8 0x6e7 0x6e8 0x7 0x7 0x7 0x7 0x7 0x7 0x6c0 0x6c0 0x6c1 0x7 0x7 0x7 0x1f99 0x1f99 0x306 0x7 0x7 0x7 0x306
上面是刚做题时候分析的,其实第一个加密还是很简单的,我们用简单表示一下,也就是经过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 ] 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 //////////////////////////X0x763 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 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}