跳转至
2022 | DownUnderCTF | misc

last digit

题目

Can you get the flag given its last digit?

nc 2022.ductf.dev 30003

last-digit.py
with open('/flag.txt', 'rb') as f:
    FLAG = int.from_bytes(f.read().strip(), byteorder='big')

assert FLAG < 2**1024

while True:
    print("Enter your number:")

    try:
        n = FLAG * int(input("> "))
        print("Your digit is:", str(n)[-1])
    except ValueError:
        print("Not a valid number! >:(")
Dockerfile
FROM python:3.10.7

RUN /usr/sbin/useradd --no-create-home -u 1000 ctf

COPY flag.txt /
COPY last-digit.py /home/ctf/

RUN chmod a+x /home/ctf/last-digit.py

RUN apt-get update && apt-get install -y socat && rm -rf /var/lib/apt/lists/*

USER ctf

CMD socat \
        TCP-LISTEN:1337,reuseaddr,fork \
        EXEC:"python -u /home/ctf/last-digit.py"

解题思路

  • Python 3.10.7 新增了 integer string conversion length limitation,在默认设置下,十进制数在 intstr 间相互转换,位数不能超过 \(4300\) 位,否则会触发 ValueError 异常
  • str(n) 可能触发 ValueError 异常,结合输入来二分求出 Flag
  • 参考 Manger's Attack 进行二分,边界 \(B\)\(10^{4300}\)

    • 通过 \(10^x\) 确定 Flag 的位数,若 \(10^{x-1} \times\) FLAG 的位数小于 \(4300\)\(10^x \times\) FLAG 的位数大于 \(4300\),那么 \(10^x \times\) FLAG \(\in [B, 10B)\),由此可初步确定 FLAG \(\in [\frac{B}{10^x}, \frac{B}{10^{x-1}})\)
    • 设当前 FLAG 的最小值 \(mn=\lfloor\frac{B}{10^x}\rfloor\),当前 FLAG 的最大值 \(mx=\lfloor\frac{B}{10^{x-1} }\rfloor\)\(y=\lfloor\frac{2B}{mx+mn}\rfloor\),则 \(y \times \lfloor\frac{mx+mn}{2}\rfloor \approx B\)
    • 向服务器发送 \(y\),若触发 ValueError 说明 \(y \times\) FLAG \(\ge B\),则设 \(mn=\lceil\frac{B}{y}\rceil\),否则设 \(mx=\lfloor\frac{B}{y}\rfloor\)
    import pwn
    from Crypto.Util.number import long_to_bytes
    
    B = 10 ** 4300
    
    conn = pwn.remote('2022.ductf.dev', 30003)
    l, r = 4000, 4300
    while l < r:
        mid = (l + r) // 2
        conn.sendafter('>', f'{10 ** mid}\n')
        ret = conn.recvline().decode()
        if '>:(' in ret:
            r = mid - 1
        else:
            l = mid + 1
    
    mn = B // 10 ** l
    mx = B // 10 ** (l - 1)
    
    while mx > mn:
        tmp = 2 * B // (mx + mn)
        conn.sendafter('>', str(tmp) + '\n')
        ret = conn.recvline().decode()
        if '>:(' in ret:
            mn = (B + tmp) // tmp
        else:
            mx = B // tmp
    
    print(long_to_bytes(mn))
    
  • 官方 WP 直接二分输入,最后根据边界求得 FLAG

    import pwn
    from Crypto.Util.number import long_to_bytes
    
    def oracle(x):
        conn.sendlineafter(b'> ', str(x).encode())
        o = conn.recvline().decode()
        return '>:(' in o
    
    conn = pwn.remote('192.168.56.104', 30003)
    
    U = 10 ** 4300
    FLAG_BITS = 1024
    
    lower = U // 2 ** FLAG_BITS
    upper = U
    # FLAG 有 1024 位,至多需要二分 1024 次
    for _ in range(1024):
        middle = (upper + lower) // 2
        if oracle(middle):
            upper = middle - 1
        else:
            lower = middle + 1
    
    f = (U + middle) // middle
    
    print(long_to_bytes(f))
    
  • 虽然没有随机算法,但是实际交互时不同连接 limitation 时有时无 (ŏωŏ) 原因不明...

Flag

CTF{14288_bits_should_be_enough_for_anybody_:)}


最后更新: 2022年9月26日 15:02:47
Contributors: YanhuiJessica

评论