2018 上海骇极杯

标签:c++, VM, hash

骇极杯的3道RE的writeup

抽空做了一下2018“骇极杯“的题目,题目比较简单

下载地址

CPP

IDA打开后很明显是c++写的,可以看到string类。

sub_4010A2把输入的每一位循环左移2位再跟位数异或,再跟常量比较,如果一致,会得到假的flag

flag is: flag{7h15_15_4_f4k3_F14G_=3=_rua!}

之后在sub_401332中,又把每一位和前一位进行了一些逻辑运算操作,刚好这学期在学数电,懂得一些逻辑运算的化简,化简后这个操作就是异或。用A代表a[j],B代表a[j-1],化简过程: \[ (A+B)\overline{AB}=(A+B)(\overline{A}+\overline{B})=A\overline{A}+A\overline{B}+B\overline{A}+B\overline{B})=A\overline{B}+B\overline{A}=A⊕B \] 总共循环了四次,最后跟常量比较。

反解一下这两个过程算出flag就行了。

1
2
3
4
5
6
7
8
9
10
a=[153,176,135,158,112,232,65,68,5,4,139,154,116,188,85,88,181,97,142,54,172,9,89,229,97,221,62,63,185,21,237,213]
for i in range(4):
for j in range(1,32)[::-1]:
a[j]^=a[j-1]
s=''
for i in range(32):
a[i]^=i
a[i]=((a[i]>>2)|(a[i]<<6))&0xff
s+=chr(a[i])
print(s)

由于涉及循环左移右移,用C写更方便一些,不过后面还是补了一个用python写的,感觉也不会有多差

cyvm

VM题,指令比较简单,要注意的是他是用栈作为寄存器的,对ebp-20h有一个0x14的偏移,每个寄存器大小是4byte,要看汇编 才能看的出,ida的反编译不能很好的解析,实际上栈上的变量应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-000000000000003C var_3C          dd ?
-0000000000000038 bytecode dd ?
-0000000000000034 var_34 dd ?
-0000000000000030 var_30 dd ?
-000000000000002C _ip dd ?
-0000000000000028 var_28 dd ?
-0000000000000024 var_24 dd ?
-0000000000000020 r0 dd ?
-000000000000001C r1 dd ?
-0000000000000018 r2 dd ?
-0000000000000014 r3 dd ?
-0000000000000010 _zf dd ?
-000000000000000C var_C dd ?
-0000000000000008 var_8 dq ?
+0000000000000000 s db 8 dup(?)

bytecode中的14代表r0,15代表r1,16代表r2,17代表r3

bytecode对应分析后的指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
		0F              |             scanf("%s",str)
10 14 20 | mov r0,20h
10 16 00 | mov r2,00h
09 24 | jmp loc_1
loc_2: | loc_2:
0A 14 16 | cmp r0,r2
02 15 16 E9 | mov r1,[str+r2]
12 16 E8 | inc r2
02 17 16 | mov r3,[str+r2]
13 16 90 | dec r2
06 15 17 45 | xor r1,r3
06 15 16 76 | xor r1,r2
01 15 16 | mov r1,[str+r2]
12 16 FF | inc r2
loc_1: | loc_1:
0A 14 16 | cmp r0,r2
0C 09 | jz loc_2
0E | return strncmp(str, s2, 32uLL) == 0
;bytecode中的E9 E8 90 45 76 FF没有任何意义,属于直接跳过的,这里就省略了

可以看出它把每一位跟后一位异或,再跟位数异或,最后和常量(s2)比较。

倒过来求解得出flag:

1
2
3
4
5
6
7
8
9
a=[0x0A,0x0C,0x04,0x1F,0x48,0x5A,0x5F,0x03,0x62,0x67,0x0E,0x61,0x1E,0x19,0x08,0x36,0x47,0x52,0x13,0x57,0x7C,0x39,0x54,0x4B,0x05,0x05,0x45,0x77,0x15,0x26,0x0E,0x62]
for i in range(32):
a[i]^=i
s=''
for i in reversed(range(1,32)):
a[i-1]^=a[i]
for i in range(32):
s+=chr(a[i])
print(s)

What is it

这题目有点误导啊,看上去还以为是个elf文件,用010 editor打开发现是pe头,先改成加个.exe后缀吧。

IDA打开后,发现先要输入一个六位小写字母字符串,然后对其算md5,得到的结果通过以下运算:

1
2
3
4
5
6
7
8
9
10
v15 = 0;
v14 = 0;
for ( j = 0; j <= 31; ++j )
{
if ( strmd5[j] == 48 )
{
++v15;
v14 += j;
}
}

然后要满足10 * v15 + v14 == 403

不多说,写脚本爆破,分几部分多开几个程序跑,记得开上睿频,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from hashlib import *
s='aaaaaa'
for a in 'opqrstuvwxyz':
for b in 'abcdefghijklmnopqrstuvwxyz':
for c in 'abcdefghijklmnopqrstuvwxyz':
for d in 'abcdefghijklmnopqrstuvwxyz':
for e in 'abcdefghijklmnopqrstuvwxyz':
for f in 'abcdefghijklmnopqrstuvwxyz':
s=a+b+c+d+e+f
smd5=md5(s.encode('ascii')).hexdigest()
count=0
for i in range(32):
if(smd5[i]=='0'):
count+=10
count+=i
if count==403:
print(s)
exit(0)
print(s)

得到字符串ozulmt

然后接下来00402757的函数是对00402626smc。先把一个跟上文中的md5有关的值作为seed,md5是固定的,那后续的rand也是固定的,很迷= =,总之动调到00402626F5就完事儿了。F5之后看到先有个输入,然后校验特定位置(开头,结尾,中间几个“-”字符),剩下的位置校验的数值也是常量(跟之前md5有关,位置又是通过rand()和固定的seed得到的),不多说,直接在校验之前设下断点,把字符串拿到([ebp-6Bh]),对其格式就有flag了。

flag这部分太简单了,就没写脚本。