r1ngs's blog

上海杯-2018-crypto

2018-11-05

AESSSS

0x00 前言

官方的环境第二天已经崩掉了,还好留下了脚本,稍作修改就可以本地搭建

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
import random
import sys
import string
from hashlib import sha256
import SocketServer
from Crypto.Cipher import AES

FLAG = 'flag{123456789012345678901234567}'
IV = 'abcdefghabcdefgh'
KEY = 'abcdefghabcdefgh'

class Task(SocketServer.BaseRequestHandler):
def proof_of_work(self):
proof = ''.join(
[random.choice(string.ascii_letters+string.digits) for _ in xrange(20)])
'''print proof'''
digest = sha256(proof).hexdigest()
self.request.send("sha256(XXXX+%s) == %s\n" % (proof[4:], digest))
self.request.send('Give me XXXX:')
x = self.request.recv(10)
x = x.strip()
if len(x) != 4 or sha256(x+proof[4:]).hexdigest() != digest:
return False
return True

def pad(self, s):
s += (256 - len(s)) * chr(256 - len(s))
ret = ['\x00' for _ in range(256)]
for index, pos in enumerate(self.s_box):
ret[pos] = s[index]
return ''.join(ret)

def unpad(self, s):
ret = ['\x00' for _ in range(256)]
for index, pos in enumerate(self.invs_box):
ret[pos] = s[index]
return ''.join(ret[0:-ord(ret[-1])])

s_box = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
]

invs_box = [
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
]

def encrypt(self, msg):
cipher = AES.new(KEY, AES.MODE_CBC, IV)
return cipher.encrypt(msg).encode('hex')

def handle(self):
if not self.proof_of_work():
return
self.request.settimeout(15)
req = self.request
flag_len = len(FLAG)
assert(flag_len == 33)
self.flag = self.pad(FLAG)
assert(len(self.flag) == 256)

while True:
req.sendall(
'Welcome to AES(WXH) encrypt system.\n1. get encrypted flag.\n2. pad flag.\n3.Do some encrypt.\nYour choice:')
cmd = req.recv(2).strip()
try:
cmd = int(cmd)
except ValueError:
cmd = 0
if cmd == 1:
enc = self.encrypt(self.flag)
req.sendall('Here is the encrypted flag: 0x%s\n' % enc)
elif cmd == 2:
req.sendall('Pad me something:')
self.flag = self.unpad(self.flag)[
:flag_len] + req.recv(1024).strip()
assert(len(self.flag) <= 256)
self.flag = self.pad(self.flag)
req.sendall('Done.\n')
elif cmd == 3:
req.sendall('What do you want to encrypt:')
msg = self.pad(req.recv(1024).strip())
assert(len(msg) <= 256)
enc = self.encrypt(msg)
req.sendall('Here is the encrypted message: 0x%s\n' % enc)
else:
req.close()
return


class ThreadedServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass


if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 23333
print 'Run in port:23333'
server = ThreadedServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()

0x01 非最优解

自己在做题的时候没有找到漏洞,而是用了一种非最优解的做法,这种做法,讲道理,可以做,但是当时受限于网络问题,脚本跑的比较慢,自己当时又不知道怎么向nc端口监听发送数据,于是找了同队的pwn爷爷帮忙用pwntools写脚本,结果到比赛结束自己的脚本也跑不全(当时已经跑出了3位了),还浪费了pwn爷爷做pwn的时间。

讲讲自己的做法,以下是我做题时对脚本的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
首先将FLAG33位填充到256位 不足的位数用chr(256-33)填充
然后经过s盒进行置换即得到flag

1)
使用aes的cbc模式 加密flag 输出16进制:
0x3478e3196c4a6a29b244380ef7cabe1030d103f3c3df019fb4ed207b849d0b6d5da5d3bf89f7ca65707c19591413de7dccddd498b8a8ab5fbae70e9ec17fdfc132a2fb63e0968d737b9839c0f7686de07251a1de0264d0ecad05749ad0d13a2c094da4a09837207c284e3ddfce2323c01ea0304e3362e06d191413fd3576657f072f823e07cf1c77ce453c4823c9d827545247254c8fedbe3c400567ca40eb047e1a7dba3962230b4cd0acc58d4b112690549f40c3553e34c38a1c2898a5eb5b33049d0032de35654639eb953b03464209809abecc70f0142f81fcf06298546ee58387795e9d59aa86a60e3a43a8d8f326f8a1e6ae9920291de72c638a9203fa
key iv都不知道

2)
flag进行invs盒置换,然后返回flag的前33位(FLAG),然后加上一个自定义输入的字符组成meg
将msg填充到256位,不足的用chr(256-msg长度)填充 然后返回经过s盒置换的东西赋值给flag

3)
输入自定义的字符串 填充到256位,不足的用chr(256-长度)填充 然后经过s盒置换,再进行aes-cbc加密输出16进制

4)
经过s盒置换再经过invs盒置换后就能还原字符串
同样的经过invs盒置换后再经过s盒置换也能还原字符串

5)
通过操作21,使得flag变成FLAG + 2230,那么通过操作1,也就是经过S盒置换后加密的flag如下:
0xdaf999ff0261053e70920760c99359c206a6162434a6c9d93c03c61a11fbd418b617d3b70c8be06c5c778031cad499af02393a15198d9c43a7ce57e7b47708dd6b1f39a243590539c6e0bac5d37aa38fa99617fd66a01afced5cb46de0bf4d1a75be8304dc43b973a22d937a432d3f96ad28b75e6b4b2e8bd4dd19863bd88e7a87d42a1f29d31566d1b474e8a61fc1891ffd8a60ff9710645b19ee56f2064b6df793885dcd7d579a5f169aba91e230ff0b48c503359bccd1e45b03e1988de76aabbcfd9d13a3dd37e76250d040e297fe30b89ce5297b1d506c5e1f6629abed9b87ef3897bded520ae8dc1289cf2d4c7a311587b30a14736af630b9edff53f48e

当时我的想法也就是:

FLAG+223个0加密的结果你是知道的,那么我就通过操作3,输出256个0加密的结果,由于cbc模式采用分组加密,如果两个明文的第一块的明文完全相同,那么输出结果的第一块密文也是完全相同的,而FLAG+223个0通过s盒置换使得FLAG的各个位置散落在cbc加密的16组里面,所以,我们只要按位置改变操作3的输入,通过判断输出的结果和FLAG+223个0的前多少多少位是否相同来依次爆破就能得到flag

那么这种方法的问题出在哪了呢?因为我们判断的依据是根据是每16个字节为一块的单位进行的爆破,而FLAG经过s盒置换后可能FLAG有几位在同一组里,这样就必须同时对这几位爆破,当时我爆出的几位都是一组里面只有这一位,也就是只有大概60种可能,所以很快,但后来FLAG有5位在同一组里面,这就意味着这一组的爆破有60的5次方种可能,想爆破出来就很困难了

贴一下比赛时候的脚本吧:

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
from pwn import *
context.log_level = "debug"

p = remote("106.75.13.64",54321)

p.recvuntil("+")
str1 = p.recvuntil(")",drop=True)
p.recvuntil("== ")
str2 =p.recvuntil("\n",drop = True)
print str1
print str2

from hashlib import sha256

for i in range(65, 123):
for j in range(65, 123):
for k in range(65, 123):
for l in range(65, 123):
x = chr(i)+chr(j)+chr(k)+chr(l)
if sha256(x + str1).hexdigest() == str2:
p.sendline(x)

a = '000000000_0u0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'

a = list(a)
for i in range(32, 127):
p.recvuntil("choice:")
p.sendline(str(3))
a[8] = chr(i)
p.recvuntil("encrypt:")
p.sendline(''.join(a))
p.recvuntil("message: ")
key = p.recvuntil("\n")
'''success("key-->"+key)'''
if key[0:130] == '0xdaf999ff0261053e70920760c99359c206a6162434a6c9d93c03c61a11fbd418b617d3b70c8be06c5c778031cad499af02393a15198d9c43a7ce57e7b47708dd':
success("flag-->"+''.join(a))
exit()

如你们所见,当时跑出了一个下划线和u= =

0x02 最优解

1
2
3
4
5
6
7
8
9
10
11
12
def unpad(self, s):
ret = ['\x00' for _ in range(256)]
for index, pos in enumerate(self.invs_box):
ret[pos] = s[index]
return ''.join(ret[0:-ord(ret[-1])])

elif cmd == 2:
req.sendall('Pad me something:')
self.flag = self.unpad(self.flag)[:flag_len] + req.recv(1024).strip()
assert(len(self.flag) <= 256)
self.flag = self.pad(self.flag)
req.sendall('Done.\n')

问题的关键出在了这个unpad函数上

我们第一次进行操作2,填充256-33=223个字符,然后经过s盒置换

但我们如果再次进行操作2,那么就会得到FLAG+223个字符(也就是ret),然后返回ret[ 0 : -ord( ret [-1] ) ],而这个ret[-1]就是我们第一次进行操作2输入的最后一个字符,如果我们输入chr(255)那么就只会返回flag的第一位,然后进行切片[:33],这里就比较恶心了,python对于超过长度的切片居然不会报错,而是返回最长长度

1
2
3
4
>>> [1, 2, 3, 4][:1]
[1]
>>> [1, 2, 3, 4][:33333]
[1, 2, 3, 4]

然后我们再输入256-1=255个0, 然后经过S盒置换,再通过操作1就能得到加密的结果

那么我们可以通过操作3输入 $c$+255个0就能得到经过S盒置换再加密的结果,对$c$进行爆破就能得到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
from socket import *
from hashlib import sha256
from re import *
from string import *

def connect(host, port):
s = socket()
s.connect((host, port))
return s

def proof(s):
mess = s.recv(1024)
print mess
print s.recv(1024)

reg = 'sha256\(XXXX\+(.+)\) == (.+)'
regexp = compile(reg)
str_12 = regexp.findall(mess)
str1 = str_12[0][0]
str2 = str_12[0][1]

dict = letters + digits
for i in dict:
for j in dict:
for k in dict:
for l in dict:
x = i + j + k + l
if sha256(x + str1).hexdigest() == str2:
print x
s.send(x)
print s.recv(1024)
return s

def get_flag(host, port):
flag_len = 33
flag = ''

for i in range(flag_len):
i += 1

s = connect(host, port)
s = proof(s)

print '2'
s.send('2')
print s.recv(1024)
print 222 * '0' + chr(256 - i)
s.send(222 * '0' + chr(256 - i))
print s.recv(1024)
print s.recv(1024)

print '2'
s.send('2')
print s.recv(1024)
print (256 - i) * '0'
s.send((256 - i) * '0')
print s.recv(1024)
print s.recv(1024)

print '1'
s.send('1')
mess = s.recv(1024)
print mess

reg = 'Here is the encrypted flag: (.+)'
regexp = compile(reg)
str_1 = regexp.findall(mess)
str1 = str_1[0]
flag = get_char(s, flag, str1, i)

return 'you made it and this is your flag! -->' + flag


def get_char(s, flag, str1, i):
dict = letters + digits + '{' + '}'

for char in dict:

print s.recv(1024)
print '3'
s.send('3')
print s.recv(1024)

print flag + char + (256 - i) * '0'
s.send(flag + char + (256 - i) * '0')
mess = s.recv(1024)
print mess

reg = 'Here is the encrypted message: (.+)'
regexp = compile(reg)
str_2 = regexp.findall(mess)
str2 = str_2[0]

if str1 == str2:
flag += char
s.close()
return flag

def AES():
host = '192.168.134.135'
port = 23333

print get_flag(host, port)

if __name__ == '__main__':
AES()

有人说socket没有pwntools好用,但是我觉得pwntools也好不到哪里去= =

RSAAAA

0x00 前言

太菜了,这个题在比赛的时候也没做出来,还是十分羞耻的卡在了第一关。。。

题目没有给在端口搭建的脚本我又懒得改写= =

0x01 第一关

题目第一关给的是这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def rsa_gen(bit_n=2048, bit_e=64):
p = getPrime(bit_n / 2)
q = getPrime(bit_n / 2)
n = p * q
e = getPrime(bit_e)
d = mod_inv(e, (p - 1) * (q - 1))
msg = int(''.join([random.choice(string.ascii_letters + string.digits)
for _ in xrange(16)]).encode('hex'), 16)
cipher = pow(msg, e, n)
return e, d, n, msg, cipher


def tell_n_d():
e, d, n, m, c = rsa_gen()
print_output("Give you a message:0x%x\nand its ciphertext:0x%x" % (m, c))
print_output("Please give me the private key to decrypt cipher")
print_output("n:")
N = int(raw_input().strip())
print_output("d:")
D = int(raw_input().strip())
if not pow(c, D, N) == m:
exit(1)
print_output("Oh, how you know the private key!")
return m

给出了m和c,只要找到一组N和D就让你过,当时脑子真的是抽了,我想的是随后找到一个比c大的N,然后爆破D,使得pow(c, D, N) == m,由于涉及到求离散对数的问题,使这个做法很难达成, 于是当时就放弃了。。。

当我看到别人随便找了一个D,然后求得n = pow(c, D) - m的时候我直接愣住了。。。

那么为了简单,D取1就行了= =

第一关就没有什么可以说的了

0x02 第二关

还是有一点意思的,看了别人的wp,听说在今年的SUCTF里面考过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def number_theory():
while True:
e, d, n, m, c = rsa_gen()
print_output("n=0x%x\ne=0x%x\nc=0x%x\n" % (n, e, c))
print_output("Now, you have a chance to decrypt something(but no c):")
C = int(raw_input().strip())
if C == c:
print_output("Nonono~")
continue
M = pow(C, d, n)
print_output("message:0x%x" % M)
print_output("Give me right message:")
MM = int(raw_input().strip())
if not MM == m:
exit(1)
print_output("Master in math!")
return m

在我们已知n, e, c的情况下去求另外一个$c_1$,使得$pow(c_1, d, n) = M$ 然后你通过这个输出的M就能得到m

如果给$c_1​$赋以下面的值,我们来看看会得到什么东西

所以可得到$pow(c_1, d, n) = 2 \times m = M$

那么通过回显的M乘以2的逆元就能得到m了

最后,IV有了,key有了,cipher有了,再通过这一步计算就能拿到flag了

1
2
3
4
5
6
7
8
9
10
from Crypto.Cipher import AES
from libnum import *

msg1 = 0x414e744b3649345a43444679634c3341
msg2 = 131947649330183564622513572431866783299
flag = 0xf34e8c903c3b3803c6f69f64d88612b1bd7b7bbcdaddc962fe0514bf1295a260a7cabd3f261869b6e49ce1088be9bdd8
cipher = AES.new(n2s(msg2), AES.MODE_CBC, n2s(msg1))
dec = cipher.decrypt(n2s(flag))

print dec

总结

长记性了,把这个socket的轮子留着,下次直接改

对于第一道AESSSS,我的关注点放在了CBC加密本身的缺陷上,而没有想到去找算法本身的漏洞,以后还是应该关注一下细节

在跑脚本之前应该先评估一下时间复杂度,不要去白白浪费时间,当发现自己的思路出了明显问题的时候,还是应该先冷静思考,找找自己忽略了的地方

参考文章

[1] 2018 上海市大学生网络安全大赛题解

Tags: CTF

1000000