2020 GKCTF writeup

这周做了一下由防灾科技学院 Ginkgo 战队主办的 GKCTF2020,题目难度不大,但是有些小地方卡住了(再加上周末睡懒觉起晚了

RE

Check in

用字符画画了一个笔记本电脑,提示说自己找开机密码.

没有仔细逆画字符画和移动的部分,直接搜字符串找到个类似base64的字符串,查找下引用发现是base58.

1
2
import base58
print(base58.b58decode(b"2i9Q8AtFJTfL3ahU2XGuemEqZJ2ensozjg1EjPJwCHy4RY1Nyvn1ZE1bZe"))

EzMachine

VM题,一共22个handler,有寄存器及栈.指令不难分析,很容易写出parser

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
code = [0x01, 0x03, 0x03, 0x05, 0x00, 0x00, 0x11, 0x00, 0x00, 0x01, 0x01, 0x11, 0x0C, 0x00, 0x01, 0x0D, 0x0A, 0x00, 0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x02, 0x00, 0x01, 0x00, 0x11, 0x0C, 0x00, 0x02, 0x0D, 0x2B, 0x00, 0x14, 0x00, 0x02, 0x01, 0x01, 0x61, 0x0C, 0x00, 0x01, 0x10, 0x1A, 0x00, 0x01, 0x01, 0x7A, 0x0C, 0x00, 0x01, 0x0F, 0x1A, 0x00, 0x01, 0x01, 0x47, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x06, 0x00, 0x01, 0x0B, 0x24, 0x00, 0x01, 0x01, 0x41, 0x0C, 0x00, 0x01, 0x10, 0x24, 0x00, 0x01, 0x01, 0x5A, 0x0C, 0x00, 0x01, 0x0F, 0x24, 0x00, 0x01, 0x01, 0x4B, 0x0A, 0x00, 0x01, 0x01, 0x01, 0x01, 0x07, 0x00, 0x01, 0x01, 0x01, 0x10, 0x09, 0x00, 0x01, 0x03, 0x01, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x0B, 0x00, 0x02, 0x07, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x02, 0x05, 0x00, 0x02, 0x01, 0x00, 0x02, 0x0C, 0x00, 0x02, 0x01, 0x00, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x02, 0x0D, 0x00, 0x02, 0x05, 0x00, 0x02, 0x0F, 0x00, 0x02, 0x00, 0x00, 0x02, 0x09, 0x00, 0x02, 0x05, 0x00, 0x02, 0x0F, 0x00, 0x02, 0x03, 0x00, 0x02, 0x00, 0x00, 0x02, 0x02, 0x00, 0x02, 0x05, 0x00, 0x02, 0x03, 0x00, 0x02, 0x03, 0x00, 0x02, 0x01, 0x00, 0x02, 0x07, 0x00, 0x02, 0x07, 0x00, 0x02, 0x0B, 0x00, 0x02, 0x02, 0x00, 0x02, 0x01, 0x00, 0x02, 0x02, 0x00, 0x02, 0x07, 0x00, 0x02, 0x02, 0x00, 0x02, 0x0C, 0x00, 0x02, 0x02, 0x00, 0x02, 0x02, 0x00, 0x01, 0x02, 0x01, 0x13, 0x01, 0x02, 0x04, 0x00, 0x00, 0x0C, 0x00, 0x01, 0x0E, 0x5B, 0x00, 0x01, 0x01, 0x22, 0x0C, 0x02, 0x01, 0x0D, 0x59, 0x00, 0x01, 0x01, 0x01, 0x06, 0x02, 0x01, 0x0B, 0x4E, 0x00, 0x01, 0x03, 0x00, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x01, 0x03, 0x01, 0x05, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00]
ip = 0
sp = 0
bp = 0
stack = [0 for i in range(256)]
R = [0 for i in range(8)]

while ip < len(code):
op = code[ip]
if op == 0:
ip+=1
elif op == 1:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[oprnd1] = oprnd2
print("%3d: "%(ip-3),"mov R%d, %d"%(oprnd1, oprnd2))
elif op == 2:
oprnd1 = code[ip+1]
sp+=1
ip+=3
stack[sp] = oprnd1
print("%3d: "%(ip-3),"push %d"%oprnd1)
elif op == 3:
oprnd1 = code[ip+1]
sp+=1
ip+=3
stack[sp] = R[oprnd1]
print("%3d: "%(ip-3),"push R%d"%oprnd1)
elif op == 4:
oprnd1 = code[ip+1]

ip+=3
R[oprnd1] = stack[sp]
sp-=1
print("%3d: "%(ip-3),"pop R%d"%oprnd1)
elif op == 5:

ip+=3
print("%3d: "%(ip-3),"puts something")
elif op == 6:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[oprnd1] += R[oprnd2]
print("%3d: "%(ip-3),"add R%d, R%d"%(oprnd1, oprnd2))
elif op == 7:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[oprnd1] -= R[oprnd2]
print("%3d: "%(ip-3),"sum R%d, R%d"%(oprnd1, oprnd2))
elif op == 8:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[oprnd1] *= R[oprnd2]
print("%3d: "%(ip-3),"mul R%d, R%d"%(oprnd1, oprnd2))
elif op == 9:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[0] = R[oprnd1] // R[oprnd2]
R[1] = R[oprnd1] % R[oprnd2]
print("%3d: "%(ip-3),"div R%d, R%d"%(oprnd1, oprnd2))
elif op == 10:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[oprnd1] ^= R[oprnd2]
print("%3d: "%(ip-3),"xor R%d, R%d"%(oprnd1, oprnd2))
elif op == 11:
ip = 3*code[ip+1]-3
print("%3d: "%(ip-3),"jmp %d"%oprnd1)
elif op == 12:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
ip+=3
R[3] = R[oprnd1] - R[oprnd2]
print("%3d: "%(ip-3),"cmp R%d, R%d"%(oprnd1, oprnd2), R[oprnd1], R[oprnd2])
elif op == 13:
oprnd1 = code[ip+1]
print("%3d: "%ip,"jz %d"%(3*oprnd1-3))
if R[3]:
ip += 3
else:
ip = 3*code[ip+1]-3

elif op == 14:

oprnd1 = code[ip+1]
print("%3d: "%ip,"jnz %d"%(3*oprnd1-3))
if R[3]:
ip = 3*code[ip+1]-3
else:
ip += 3
elif op == 15:

oprnd1 = code[ip+1]
print("%3d: "%ip,"jg %d"%(3*oprnd1-3))
if R[3]<=0:
ip += 3
else:
ip = 3*code[ip+1]-3
elif op == 16:

oprnd1 = code[ip+1]
print("%3d: "%ip,"jb %d"%(3*oprnd1-3))
if R[3]>=0:
ip += 3
else:
ip = 3*code[ip+1]-3
elif op == 17:
# input
buf = [0 for i in range(17)]
buf = list(b"?lag{1234567890a}")
R[0] = len(buf)
ip+=3
print("%3d: "%(ip-3),"input")
elif op == 18:
length = code[ip+2] - code[ip+1]
buf[code[ip+1]:code[ip+2]] = [0 for i in range(length)]
ip=3
print("%3d: "%(ip-3),"memset")
elif op == 19:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
R[oprnd1] = stack[bp+R[oprnd2]]
ip+=3
print("%3d: "%(ip-3),"mov R%d, [bp+R%d]"%(oprnd1, oprnd2))
elif op == 20:
oprnd1 = code[ip+1]
oprnd2 = code[ip+2]
R[oprnd1] = buf[R[oprnd2]]
ip+=3
print("%3d: "%(ip-3),"mov R%d, buf[R%d]"%(oprnd1, oprnd2))
elif op == 0xff:
print("%3d: "%(ip-3),"death")
exit(0)

算法也不难,根据范围做简单的异或/加减运算,然后转hex.

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
res = [7, 13, 0, 5, 1, 12, 1, 0, 0, 13, 5, 15, 0, 9, 5, 15, 3, 0, 2, 5, 3, 3, 1, 7, 7, 11, 2, 1, 2, 7, 2, 12, 2, 2][::-1]
cipher = []
for i in range(17):
cipher.append(res[2*i+1]*16+res[2*i])
plain = [[] for i in range(17)]
print(cipher)
for i in range(len(cipher)):
tmp = cipher[i]
# print((i-1)^71)
if 97<=((tmp-1)^71)<=122:
plain[i].append((tmp-1)^71)
print(i)
if 65<=((tmp+1)^75)<=90:
plain[i].append((tmp+1)^75)
print(i)
if tmp<65:
plain[i].append(tmp)
print(i)
if 90<tmp<97:
plain[i].append(tmp)
print(i)
if 122<tmp<127:
plain[i].append(tmp)
print(i)
for i in plain:
print(bytes(i))
print((plain))

BabyDriver

驱动逆向.Windows搞得不多,由于这题算法比较简单因此纯静态就能做了,不然还得想办法搭双机调试环境.

我也不太熟悉keyboard hook,做的时候也是瞎鸡儿做的.(以下内容分析的不一定正确,有空再研究...)总之就找到了一个关于按键的回调函数,很容易分析出是走迷宫.根据键盘扫描码应该是ikjl对应上下左右,(发现有个地方加6了,一开始以为是要加6对应wsad,因为看了一篇文章是hook KeyboardInterruptService的)交了下发现不对,也没找到其他的什么地方,又试了一下大写就对了.

1
2
from hashlib import md5
print("flag{"+md5(b"LKKKLLKLKKKLLLKKKLLLLLL").hexdigest()+"}")

Chellys_identity

搜了下找到罪恶王冠,由于没看过就没继续看了

调试一下很容易分析出算法.

求小于n的斐波那契数质数(我也不知道我做题的时候怎么想到是斐波那契数的,可能是看到2 3 5了吧2333...,总之是dump下来的)之和再异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
res = [0x000001B6, 0x00000498, 0x00000441, 0x00000179, 0x00000179, 0x00000640, 0x0000039C, 0x00000179, 0x0000064A, 0x0000039C, 0x0000027D, 0x0000027F, 0x00000178, 0x00000236, 0x00000344, 0x0000033E]
fib = [0x00000002, 0x00000003, 0x00000005, 0x00000007, 0x0000000B, 0x0000000D, 0x00000011, 0x00000013, 0x00000017, 0x0000001D, 0x0000001F, 0x00000025, 0x00000029, 0x0000002B, 0x0000002F, 0x00000035, 0x0000003B, 0x0000003D, 0x00000043, 0x00000047, 0x00000049, 0x0000004F, 0x00000053, 0x00000059, 0x00000061, 0x00000065, 0x00000067, 0x0000006B, 0x0000006D, 0x00000071, 0x0000007F]
dic = {}
def get_map(a):
s = 0
for i in fib:
if i<a:
s+=i
return s^a
# print(hex(get_map(ord('f'))))
for i in range(127):
dic[get_map(i)] = chr(i)
print(dic)
s = ""
for i in res:
s+=dic[i]
print(s)

WannaReverse

此题赛后解出

看这个名字应该是类似永恒之蓝的玩意,就没直接run了.

直接分析WannaReverse.exe静态编译的,但是熟悉函数结构的话不难分析出一些常见函数.main开头用srand(time(0))设定种子,然后用rand()%10生成32字节伪随机密钥.用exe中硬编码的公钥加密32字节密钥,然后用伪随机的32字节密钥AES加密flag.最后把WannaReverse字符串,RSA加密的AES密钥,密文写入文件.

一开始尝试分解N,无果

然后直接硬爆密钥了.想用flag筛选,筛选不出.

测试一下题目加密的结果,发现是用00padding的.然后用 * n做结尾筛选,三个的时候筛选出了flag.一看原来是Unicode编码的,所以用flag字符串筛选不到

这里说一下,Win7的记事本在保存文件的时候可以指定Unicode编码,开头的,同时也可以指定UTF-8或Unicode大端序或ANSI.

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

from Crypto.Cipher import AES
from base64 import *

def gen_key(seed):
k = b""
for i in range(32):
seed = seed * 214013 + 2531011
k+=bytes([0x30+(((seed>>16) & 0x7fff)%10)])
return k
# t = 1590310000
t = 1589530000

while True:
key = gen_key(t)
# print(key)
aes = AES.new(key, mode=AES.MODE_ECB)
cipher = b"\\\xbc\xea\x89\xba+\x18\'y?\x13\n\x8a\x97\xb4\x9b\xcdx\x9b\xd85\x92\x05EL\"\xa5i7\xebn+\x0e\xbd\x84\x0f\x91a8\xf6\xf1\xba\x99\x19Ar\x07\x91\xf0&h\x06a&\\ 5\xdd\xcf\xfcwWT\x81\xf2\xf2\xe4\xaf\xbf\xa2\x1d)\xael\x08;v\x1bf\xb8\xfer\xcb\xd6\x94\xc3\xd5j\xe7\x0cz(\xdc\xbc\xac\x80"
if aes.decrypt(cipher).endswith(b"\x00\x00\x00"):
print(t)
print(aes.decrypt(cipher))
print(aes.decrypt(cipher).decode("utf-16"))
exit()
t-=1
if t%10000 == 0:
print(t)

DbgIsFun

此题赛后解出

main函数利用SEH捕获int 3断点,改变控制流.

输入长度不为28会进入假逻辑(先减去28,然后检验EFLG为0x246,即是否相等)

真逻辑只是设置一个地方为1,然后sleep5秒,就没了

其实这里Sleep应该反应过来是有多线程在干什么事情,应该搜一下CreateThread之类的

然后这里就卡住了.比赛后重新看一遍,找到TLS回调函数,里面有SMC,这是正常解法(不知道为什么x86dbg没有在tls回调函数断下来,还是Windows做的少.一般情况下x86dbg在TLS断下来后我才会去看,很少主动找TLS)

解完SMC,这里是正常加密逻辑.先对sub_401540求和(防止patch与断点),然后RC4

1
2
3
4
5
6
7
8
9
from Crypto.Cipher import ARC4
res = bytes([0x2D, 0xD4, 0x0F, 0xD0, 0x54, 0xEE, 0x75, 0xD0, 0xE0, 0x30, 0x96, 0xE1, 0x79, 0x8A, 0xE0, 0xFE, 0x18, 0x3A, 0x27, 0xE7, 0x2F, 0x86, 0xC9, 0xFE, 0x66, 0x43, 0xA7, 0x75])
key = b"GKCTF"
rc4 = ARC4.new(key)
print(k)
for k in range(256):
rc4 = ARC4.new(key)
print(bytes(list(map(lambda x: x^k, rc4.decrypt(res)))), k)
# flag{5tay4wayFr0m8reakp0int}