DiceCTF 2024 Quals Writeup

この大会は2024/2/3 6:00(JST)~2024/2/5 6:00(JST)に開催されました。
今回もチームで参戦。結果は332点で1040チーム中175位でした。
自分で解けた問題をWriteupとして書いておきます。

welcome (misc)

Discordに入り、#rulesチャネルのメッセージを見ると、フラグが書いてあった。

dice{flag!}

dicedicegoose (web)

HTMLソースを見ると、スクリプトに以下の部分がある。

    :
    :

  let player = [0, 1];
  let goose = [9, 9];

  let walls = [];
  for (let i = 0; i < 9; i++) {
    walls.push([i, 2]);
  }

  let history = [];
  history.push([player, goose]);

    :
    :

  function encode(history) {
    const data = new Uint8Array(history.length * 4);

    let idx = 0;
    for (const part of history) {
      data[idx++] = part[0][0];
      data[idx++] = part[0][1];
      data[idx++] = part[1][0];
      data[idx++] = part[1][1];
    }

    let prev = String.fromCharCode.apply(null, data);
    let ret = btoa(prev);
    return ret;
  }

  function win(history) {
    const code = encode(history) + ";" + prompt("Name?");

    const saveURL = location.origin + "?code=" + code;
    displaywrapper.classList.remove("hidden");

    const score = history.length;

    display.children[1].innerHTML = "Your score was: <b>" + score + "</b>";
    display.children[2].href =
      "https://twitter.com/intent/tweet?text=" +
      encodeURIComponent(
        "Can you beat my score of " + score + " in Dice Dice Goose?",
      ) +
      "&url=" +
      encodeURIComponent(saveURL);

    if (score === 9) log("flag: dice{pr0_duck_gam3r_" + encode(history) + "}");
  }

    :
    :

historyにplayerとgooseの座標の位置が入る。スコアが9になるためには8個動かして隣になるしかない。このパターンには1パターンの移動しかない。
経路は以下のようになる。

[[0, 1], [9, 9]]
[[1, 1], [9, 8]]
[[2, 1], [9, 7]]
[[3, 1], [9, 6]]
[[4, 1], [9, 5]]
[[5, 1], [9, 4]]
[[6, 1], [9, 3]]
[[7, 1], [9, 2]]
[[8, 1], [9, 1]]

encode(history)は経路の数値をbytes文字として連結する。上記の経路の場合、以下のようになる。

\x00\x01\x09\x09...\x08\x01\x09\x01

あとは"dice{pr0_duck_gam3r_"にこのbytes文字列のbase64エンコードしたものを結合し、"}"で閉じればよい。

#!/usr/bin/env python3
from base64 import *

route = [
    [[0, 1], [9, 9]],
    [[1, 1], [9, 8]],
    [[2, 1], [9, 7]],
    [[3, 1], [9, 6]],
    [[4, 1], [9, 5]],
    [[5, 1], [9, 4]],
    [[6, 1], [9, 3]],
    [[7, 1], [9, 2]],
    [[8, 1], [9, 1]]
]

flag1 = 'dice{pr0_duck_gam3r_'

bytes_flag2 = b''
for i in range(len(route)):
    bytes_flag2 += bytes([route[i][0][0]])
    bytes_flag2 += bytes([route[i][0][1]])
    bytes_flag2 += bytes([route[i][1][0]])
    bytes_flag2 += bytes([route[i][1][1]])

flag2 = b64encode(bytes_flag2).decode()

flag = flag1 + flag2 + '}'
print(flag)
dice{pr0_duck_gam3r_AAEJCQEBCQgCAQkHAwEJBgQBCQUFAQkEBgEJAwcBCQIIAQkB}

winter (crypto)

サーバの処理概要は以下の通り。

・wots = Wots.keygen()
 ・wots.sk: 32個のランダム32バイト文字列の配列
 ・wots.vk: skの各値xに対して256回sha256した値の配列
・msg1: 入力→hexデコード
・sig1 = wots.sign(msg1)
 ・m: msgのsha256
 ・sig = b''.join([self.hash(x, 256 - n) for x, n in zip(self.sk, m)])
 ・sigを返却
・sig1を16進数表記で表示
・msg2: 入力→hexデコード
・msg1とmsg2が同じ場合、終了
・sig2: 入力→hexデコード
・wots.verify(msg2, sig2)がTrueの場合、フラグを表示

sha256の各バイト文字ができるだけ256に近い値にできるmsg1を探す。sha256の各バイト文字列がそれより小さい値にできたら、その差分だけsha256をかければsig2を算出できる。

#!/usr/bin/env python3
import socket
from hashlib import sha256

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

hashes = []
found1 = False
for i in range(100000):
    h = sha256(str(i).encode()).digest()
    for j in range(len(hashes)):
        found2 = True
        for idx in range(32):
            if h[idx] > hashes[j][idx]:
                found2 = False
                break
        if found2:
            found1 = True
            msg1 = str(j).encode()
            msg2 = str(i).encode()
            break
    if found1:
        break

    hashes.append(h)

h_msg1 = sha256(msg1).digest()
h_msg2 = sha256(msg2).digest()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('mc.ax', 31001))

data = recvuntil(s, b': ')
print(data + msg1.hex())
s.sendall(msg1.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
sig1 = bytes.fromhex(data.split(': ')[1])

data = recvuntil(s, b': ')
print(data + msg2.hex())
s.sendall(msg2.hex().encode() + b'\n')

sig2 = b''
for i in range(len(sig1) // 32):
    sig = sig1[i*32:i*32+32]
    count = h_msg1[i] - h_msg2[i]
    for j in range(count):
        sig = sha256(sig).digest()
    sig2 += sig

data = recvuntil(s, b': ')
print(data + sig2.hex())
s.sendall(sig2.hex().encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

give me a message (hex): 3437313835
here is the signature (hex): 74072609ad62d9a6494a96275f7d684596bb6cd284a95133af3bb111f4378332679d8f2eb2e4d53b5a25eb11ddbfe7d2a5c9aa746b858aeadae75979f8635668549ae04d56a057ed2e3b1cffff4b1b7692aed0958a9efe639ef8d1af011bbb60f7175b9c62e901853e19ad783deb0309dfa01f0f9cad5978bee117b5c810d8dcfda6a6fcd33d9fd7443054caea867337a1acc2048fd65ae88ea381e6fb43fa637efc019c909605933d963496275bfb95aa4124dd8c6dca8ca7d9bddac3d1b874b088378e757c97a4e948f1fd37b4d9c545ba2ce47bff318ee0e1258d6da61abd51f464f158a85f5c7c080cb7489edc8dedc31a5c251ec325dc410415e1b50a46d6272c86325bc20a87d37db93d471d1f3e3ab7237dadd9e04c001b4a171b12a37434ef8db92a63f886056db93e336260d36c3e5dea86f02611519ef35e370e6e3b9fc8a541d71a1b6e62e4ebd8a2e7c040bc1a07f3eecd095cf1476f3cddc6167b833838f383b629e317b373127201d105ce49dfd065aaa2d79dad6f0b005b8a0ba6f7bf758dd758971de4ec91e57fe896513d14ef47bf164433814f3da2d472997c790dad4fe8b292f8f327d870778042ac32dd95e661e64e6c4b20b549b4fd56e55094fd69217060f907c4aa10eb18fccb0f1279d4a48c21aedc7074dc9ec7324f3a7f2f92cd0975bc394f1a1b12d18cfdafb1254ecf8b524ed980b87f2e75f2c7e54f8990920c5f56d2bad00baf4469af06d5dd60a87780bd9e01e48c06ae6704b1e86e8bdabf9c355475bd0324361f1eb90504dd006bc4535d5bf146e6e699f55d95a504ebf6784e47c068aeb916710fc2c7614a76ad127d93e150c3f7471698803148fc5e3da77d9ebdc97ee17aa9d78874edfd7e938b2091395dd7e648505e415306bd0e5c8977d740e7e32bfdbe5c6d6328055390f98d78b89a67cdc34f123c9c981168a29f2da349bc8430f01f5ecf0af410476806e379e81aa6386c3c92bda977c4f3fe588419a606b20a14fda9e55376354caaedccaf920c7d2fd52d411420bd8523d9a41c63279c0ac1def9ff39f660fa1e91cd5c26bc933232771898a0ec86f0936e69b9497a9c1911e27302c2f1685c48a0c5c9944d004ebe362c5ed4bc938e997cd9bde892c6671675ff407a50bfbe0306cf87b2faebbba0338244fc42214e20513a9f2bb71df8f6bf1309de7b86680568b1360141f37eb7e5a51b8964a32628b2d6b9fb2b78a63de925ef9aec161f54c2d4442ad9c3aee8fd4bd3fbe30b80fea242de0db55d5c33020a74e3440893c9e4b174659d313c7ae09248a1e55d8acc64aea3d12802720b471650f0c68efffa998bbeb21e3ef1908ea6c210289f5a32270cb9ae6953c4512f3293e6a76122f4908964e3f240708fe4285aad09fdaec3f4e4114c5b4c1e33f2be953e04cc7872c4766e1258689c37c4
give me a new message (hex): 3535373536
give me the signature (hex): 1a07e3c432ee75be1c1b815639823120962d55c528e5d86cb35d3a4c6ca95800cc6e6a09d8a2114342199a9fc254355cc7285dbd3fa70d1c206f096b2aae1dcd4328a3541b1ba39f3a2438df003721ad0bce86e950611d2b96f6e7fffc81ac0d675fcdb611f4404121ea5069df04261de15533d67106baa31e14d735a3185aa237d52564b9ad882b2c29515c4502ef455b7803a8c0da2095380ee19dd542269696ce11d007aed1d243f5a61d13a2155a46af3932af1236d3e3ad3a6c7cf2f36c9e5672183d5fd16775b3d3ffa5198fa7c908b10eef57df801288556f389071bd6505b4c57192d14ca2826271d5ffec158c6ed3fb363403cfe8bf376f5609df2822155339ea1f720d93192b8c2b0f7582d961e5b2e5f1ad1a3d25a17c116a4b5d8a04dd02511c8c212c88b3f52efb01d6a998eb2a81cf7ec88c336f734edf640efed435278dee1e830ab2815a13c6efecf93fcac2a2b12c7fcd8d4163be6edc0cd9d8f51f0149c709f9a9756205dccb98c7851422125c57ddd05419e845ab82cb6d75dcbbc15bb8ba938c53a72349d24aaab06ad6038ef22004cec8fb97aadd5e78af530cc1c56d172c7c5261995a27bd80f82ebe5ca5f3d0369b7f111efb5ebbfa0c2b23c6cf14642938baa90a903fc072af1c9441b1c269134d573d5da3ffadff6b6e234fd794e4c446bde3b0c6b9ada2a4aa7f18ff36748d6aa9852448bcc26122f199919876b7056266f48f8488e1226c53295937fcb293b895fb9ace2364705e7d45c68ed4e11b1e3c762431858ac902d7a3a4bc0c60b69c12ff0e1ce12b602bfc7023b00fc753d8248a9a38102a0f86ea0f9f856bcb6e3498d62df2ba40944cedb65bca7607b965655d7e8a055a51466953594344e58a1d98881732ab7faf99f9106b2471f1b8c32e33420a18a4b5635a3de40336a9a2b4b127c4919f8a21eb861278d30e7bfa8ccb1132a503bd805c88516ce85a3f2bef89b78adce5e9a38a227d097b8fd7b560a48ed074d306efa5d16ae8c6e2ce5133eb2cdff12d4e50d2c1632adc49fde9018e719972af47ff20824da07eed6446f8e3df8599b5faf19bf81fa667181df0e489254e55f82a7155c69275a153e2b01cc39ba94c30a472d23b1bd3285fb863e05b6a11fe3130501aa179acdaa6a6b20ea4292a1960275e590eaa45f6649e4badcbe7bc1009fe235f31c4150780ec25aa7a9a295de4156ebee4de0cb7e5fde6d4434aeb32c0762ec1863151175dbff1ff831b5b430c077afadfe9b453feb51c0c224234009a726d106e188831c1b9299685f0e0c053d5a9754812187201870af7fd791fdf4a1d90b188b0a3c9151cb30b5bb61b4d42ac6aa37917cf9a66ed7b0aa59a8b3b581fc400b73aa178448ebff23749c84e6fe98b3f4979c5b47e7b9ea711134a281a5ca2b363bb9d0653731ce7660347db57ee
dice{according_to_geeksforgeeks}
dice{according_to_geeksforgeeks}

survey (misc)

アンケートに答えたら、以下のように表示された。

Here's the &#128681;: https://static.dicega.ng/content/survey-ufskhigtw7meybvo.txt

ここにアクセスしたら、フラグが表示された。

dice{thanks_for_playing_dicectf!!!!!}