比赛期间没有什么时间,赛后做的题。
TCP
这题最难,耗时最久,好像做出来的人不多。
程序开始有个初始化随机数的过程,数据写入qword_5060开始的48个字节。
这里是主函数,连接到服务器以后,先接收32个字节(4个QWORD)的公钥,然后再用公钥将qword_5060处的随机数据加密发送给服务端。然后开始接收服务端的数据并执行相应的命令。
先看下sub_2090函数,这里摸索了很久才弄明白是RSA加密的函数:
powmod函数里使用了__modti3函数,开始不知道是干啥的,研究了好一会,才发现是取模的函数。
下面的解密函数就很简单,超过16个字节的用TEA解密,16个以下的用异或,分别使用qword_5060处的前32个字节和后16个字节做密钥。
取出抓包流量中的数据流,对数据流尝试解密后得到如下的分组:
其中命令部分有3个参数,参数1是命令代码值,参数2是命令参数,参数3是数据长度。
命令代码1是显示字符串,命令代码2是将解密的数据存入100*命令参数的地址,比如0201就表示将数据写入100地偏移地址。
命令代码5是执行,0508就是表示从800地址开始执行,主要PASSWORD的判断逻辑就在这部分数据里。
因此,我先把0x243个字节的数据解密,再将解密后的代码写入内存,然后用IDA反编译,得到如下程序:
可以看出来这段代码分别使用了400,500,600和700偏移处的数据,判断出结果以后,再把100或者200处的字符串数据复制到300中,然后调用命令代码4的输出功能将300处的结果显示出来。
这样就可以解密得到flag,代码片段如下:
enc=bytes.fromhex('63424fb4921f224eb2d445183f9bc6541d1ac77072a6e616e6826cbbebe96af229e79620462869e51686d4d523f782b2e5b43a769403bf62f1b87512013f95d068dc04beb337f8741b471386a7746ebc7b9c7a9568ed9b590936cdc533f6f943e3a37966362b62092ce5a3c53a95b33d0ff1ac2c5800b67b8cc40d614b8df74d4114d4bb7104b545ac7d0b9eb606578fd63561578740ce7eeaa189baacd436ae')
enc=teadec(enc,keys)
a400=[]
for i in range(len(enc)//4):
a400.append(struct.unpack('<I',enc[i*4:i*4+4])[0])
enc=bytes.fromhex('ecc4942bbc4e2bbec5c4adb791a7096298f6347c4a277364cea894234bdcf69811f31ce644bae10edc0de4ccc200a44fa0e2faa4d2eb3b28dcecc3a468efdbfa7de2728b27dfafd1a5df4803a7986cfa768fb1f9751ba1a7d47c9a6928978810d76dceb819738f4684637d3ddd2cc41e2a4585a366d4a46b32db59508f34bf5c654be7b5c86e24cca5d1013738c32ba9b6083e76e0f93c80fe712261841e5463')
enc=teadec(enc,keys)
a500=[]
for i in range(len(enc)//4):
a500.append(struct.unpack('<I',enc[i*4:i*4+4])[0])
enc=bytes.fromhex('60fd338fdcecb096e26a56603082a58f5b1fb0ffb7fc8faac33589ec52ed02256aba88b2c2836433a5d146c6efe784d47da7a7ffc4256f3dce73314c0171f89e9a4f6a95f59e8460d2b65fe178daba5faf8d34d99e32a50aefbbb7ed9c9507765958870dd2e1836fe608215ca753a7125f3cb86ac32d1149cf2a144b873fc7a96914d376ed2fe88d36a609596a68d8bb76e71d1b81a8db3618271f002ff6fb58')
enc=teadec(enc,keys)
a600=[]
for i in range(len(enc)//4):
a600.append(struct.unpack('<I',enc[i*4:i*4+4])[0])
enc=bytes.fromhex('b72d657abc1a3c2133fa45fd834d28fce3b5bdd8f111bfa61386a306aa74598ea6e5022f4aac8d9acd442c44465eb334c26d060dfc8f4f59c13f3225a5ea111f9e3e9ef4c75f40b85c43dbdd5d30970cde507cd96fa6a88970e23c1dc1cb9a1eab2f4cc16444226aff6c49dd13e030240ea32267a1699b5b8c83d05c1cc257f5028d649e2b58d96a1f59fcd42f027179e7400f2c64c3063ae1f9c496ec019eba')
enc=teadec(enc,keys)
a700=[]
for i in range(len(enc)//4):
a700.append(struct.unpack('<I',enc[i*4:i*4+4])[0])
for i in range(40):
x=((a600[i]<<16)+a700[i]-a500[i])^a400[i]
print(chr(x),end='') #DASCTF{5rOV562J5Y5pu+5amn6Zuv2B5aSa5Liq}
controlflow
代码采用ROP方式去跳转和运行
执行完主函数就会跳转到sub_AD1220和sub_AD11A0、sub_AD1100这样
调试跟踪下就清楚逻辑了,然后写出来解密代码:
a=[0x00000CCF, 0x00000CC0, 0x00000CFC, 0x00000CD8, 0x00000D23, 0x00000D11, 0x00000DC8, 0x00000D7D, 0x00000DAA, 0x00000E2B, 0x00000E7C, 0x00000E5B, 0x00000EA9, 0x00000ECA, 0x00000F5A, 0x00000F5A, 0x00000FB1, 0x0000104D, 0x00001095, 0x0000117C, 0x0000137D, 0x000012F3, 0x0000142E, 0x0000141C, 0x00001233, 0x00001287, 0x000011F4, 0x00001758, 0x00001461, 0x0000122A, 0x00001782, 0x000017F7, 0x00001911, 0x0000194D, 0x00001A10, 0x00001AEB, 0x00001B90, 0x00001CE6, 0x00001DE2, 0x00001ED2]
for i in range(10,30,2):
a[i],a[i+1]=a[i+1],a[i]
for i in range(40):
if a[i]%3!=0:
raise(i,'mod3 error')
a[i]//=3
a[i]+=i
for i in range(20):
a[i+10]^=i*(i+1)
for i in range(40):
a[i]-=i*i
a[i]^=0x401
print(bytes(a)) #DASCTF{TWpnemRuSTRkVzVsWVhOMmJqZzNOREoy}
webserver
这个程序好像是用的github上的otapp框架写的,去掉符号确实也不方便理解代码。主函数逻辑如下:
先注册了几个web接口,route包括/,/Flag,/Check
测试的时候只有/能返回welcome DASCTF,其他的接口均返回403禁止。
搜索flag字符串的时候找到了几个Handler,应该就是接口处理函数定义的地方
找到CheckHander的入口sub_40617E,真正的flag判断逻辑在这里:
这里sub_404955是一个字符串取反解密的函数
判断部分在下图的紫框中的代码,就是要让v16经过一系列处理以后变成0:
其实可以看出就是矩阵乘法,v17是从unk_61B0E0复制得到,然后写代码解密即可:
a=[0x00000017, 0x0000000D, 0x00000004, 0x00000030, 0x00000029, 0x00000029, 0x0000002A, 0x00000021, 0x0000001E, 0x00000003, 0x00000045, 0x00000001, 0x0000000D, 0x0000002D, 0x00000029, 0x00000040, 0x00000008, 0x00000050, 0x0000000F, 0x0000002A, 0x00000038, 0x00000013, 0x0000003E, 0x00000046, 0x00000017, 0x0000003F, 0x0000001E, 0x00000044, 0x00000011, 0x00000038, 0x0000005C, 0x0000000C, 0x00000010, 0x00000040, 0x0000001F, 0x00000003, 0x00000011, 0x00000047, 0x0000003A, 0x00000009, 0x00000040, 0x00000053, 0x00000047, 0x00000034, 0x00000063, 0x00000059, 0x0000004C, 0x00000044, 0x00000001, 0x00000063, 0x00000010, 0x00000010, 0x00000034, 0x0000002B, 0x00000000, 0x0000002C, 0x00000032, 0x00000020, 0x00000032, 0x0000001F, 0x00000014, 0x0000003F, 0x00000002, 0x00000063, 0x00000000, 0x00000039, 0x0000004F, 0x0000002B, 0x00000047, 0x00000013, 0x00000050, 0x0000005C, 0x0000005D, 0x0000003A, 0x00000054, 0x0000004A, 0x00000051, 0x0000002D, 0x00000037, 0x00000015, 0x00000001, 0x00000063, 0x0000001E, 0x0000001C, 0x00000038, 0x00000001, 0x0000000C, 0x0000004D, 0x0000005C, 0x00000004, 0x00000025, 0x00000043, 0x0000003C, 0x00000036, 0x00000033, 0x0000004F, 0x00000026, 0x00000057, 0x00000030, 0x00000010]
v16=[0]*40
v16[0] = 33211;
v16[1] = 36113;
v16[2] = 28786;
v16[3] = 44634;
v16[4] = 30174;
v16[5] = 39163;
v16[6] = 34923;
v16[7] = 44333;
v16[8] = 33574;
v16[9] = 23555;
v16[10] = 35015;
v16[11] = 42724;
v16[12] = 34160;
v16[13] = 49166;
v16[14] = 35770;
v16[15] = 45984;
v16[16] = 39754;
v16[17] = 51672;
v16[18] = 38323;
v16[19] = 27511;
v16[20] = 31334;
v16[21] = 34214;
v16[22] = 28014;
v16[23] = 41090;
v16[24] = 29258;
v16[25] = 37905;
v16[26] = 33777;
v16[27] = 39812;
v16[28] = 29442;
v16[29] = 22225;
v16[30] = 30853;
v16[31] = 35330;
v16[32] = 30393;
v16[33] = 41247;
v16[34] = 30439;
v16[35] = 39434;
v16[36] = 31587;
v16[37] = 46815;
v16[38] = 35205;
v16[39] = 20689;
data=[]
for i in range(10):
d=[]
for j in range(10):
d.append(a[j*10+i])
data.append(d)
import numpy as np
key=[]
for i in range(4):
d=[]
for j in range(10):
d.append(v16[j+i*10])
key.append(d)
A=np.matrix(data).I
B=np.matrix(key).T
x=np.dot(A,B)
y=x.T.round().tolist()
print(''.join(chr(int(i)) for i in y[0]+y[1]+y[2]+y[3])) #DASCTF{CI5ZCM5piv5aaC5L2V5pS26LS555qE5Y}